# Serialize.Linq turns an expression tree into text you can send to a server

> Serialize.Linq is a .NET library that serializes and deserializes LINQ expression trees to XML, JSON or binary, for the cases where a query has to cross a network boundary or survive in a database. Version 4 removed the binary serializer and added a type allow-list for untrusted payloads.

**esskar/Serialize.Linq** — C# library to serialize LINQ expressions

- Repository: https://github.com/esskar/Serialize.Linq
- Stars: 546 · Forks: 110
- Language: C#
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/esskar-serialize-linq

## Expression trees do not survive a boundary on their own

Serialize.Linq is a .NET library that provides functionality to serialize and deserialize LINQ expressions, and the README states the use case plainly: sending LINQ expressions over the wire, or persisting them in a database.

That problem is easy to underestimate. A query written as a lambda is a delegate until you look closely at it, and what you actually hold is an expression tree, a data structure of nodes describing the call. It cannot go into a JSON body by itself, and it cannot be stored as a string without losing the tree structure. So if a client has to hand a filter to a server that will run the query against a database, one of the two sides has to convert that tree into something transportable.

That is the gap this library fills. The feature list names the formats, XML, JSON and binary, and the types it handles, binary, unary, member access, lambda and more. The design is described as extensible, so you can add support for custom expressions that the library does not know about.

The primary language is C#, the licence is MIT, and the repository is not archived with the last push on 2026-08-12. There are no GitHub releases, so the version you consume comes from the package feed rather than from a tag list.

## Text in, text out, and why the round trip matters

The usage example in the README is small enough to read in one go, and it shows the shape of the whole API.

```csharp
// Create an expression
Expression<Func<int, bool>> expression = num => num < 5;

// Create a serializer
var serializer = new ExpressionSerializer(new JsonSerializer());

// Serialize the expression
string serializedExpression = serializer.SerializeText(expression);

// Deserialize the expression
var deserializedExpression = serializer.DeserializeText(serializedExpression);
```

The interesting design choice is that the serializer is constructed with a format strategy, and JsonSerializer is one of them. The JsonSerializer in the name is not the System.Text.Json instance you would use for a payload; it is the library's own pluggable formatter that knows how to write expression nodes as JSON. So the same ExpressionSerializer wrapper drives whichever format you hand it.

And the API is symmetric: SerializeText and DeserializeText. A single method pair that takes an expression and returns a string, and takes that string back and returns the tree. That symmetry is the reason this kind of library is worth having, because a one-way serializer would still leave you stuck with a stored blob you cannot read back into a query.

For the database case, the same string is what you would store in a column. For the wire case, it is the body you would post. The formats line up with the destinations, and that is the reason the library offers three.

## A type filter, because a deserialized expression is executable code

This is the part of the README that deserves the most attention, and it is a later addition rather than something the library always had.

Deserializing an expression reconstructs Types by name from the payload. When the payload is untrusted, you have to supply an ITypeFilter on an ExpressionContext to allow-list the permitted types. The README compares this to the equivalent of BinaryFormatter's SerializationBinder, and any type the filter rejects throws a TypeNotAllowedException.

```csharp
using Serialize.Linq;
using Serialize.Linq.TypeFilters;

// Only allow the types your expressions actually use.
var filter = new AllowedTypesFilter(typeof(int), typeof(bool), typeof(object), typeof(Func<,>))
    .AllowNamespace("MyApp.Models");

var context = new ExpressionContext(filter);
var deserializedExpression = serializer.DeserializeText(serializedExpression, context);
```

The reasoning behind the feature is the same reasoning that makes this worth an entire section. A deserialized expression is not data, it is a program. Resolving a Type by name from a payload means the payload decides which types exist in the receiving process, and without a filter you are trusting the sender to name only types you would have chosen yourself.

The filter implementation has three details worth knowing. AllowedTypesFilter matches explicit types, and you pass the open generic definition for generics, for example typeof(List<>), and it also matches whole namespaces, which is why the example calls AllowNamespace. DelegateTypeFilter wraps an arbitrary Func<Type, bool> predicate for cases that do not fit the list shape. And the filter is checked for every resolved type, including each generic argument, so a Func<int, bool> cannot smuggle in an unexpected argument type through the generic parameter.

With no context and no filter, all types resolve as before. That backward compatibility is deliberate, and it is also the sharp edge: a payload deserialized without a filter is exactly as dangerous as it was before the filter existed.

## Version 4 removed the binary serializer outright

The breaking changes section has one entry, and it is not a deprecation with a migration path.

BinaryFormatSerializer has been removed due to security concerns with BinaryFormatter, and the README links to the BinaryFormatter Security Guide for the detail.

The removal follows the pattern the wider .NET ecosystem set, where BinaryFormatter was switched off by default and then deprecated and then removed, because deserializing an untrusted stream with it is a well known way to execute arbitrary code. For a library whose entire purpose is to reconstruct executable structures from a payload, keeping a format that deserializes arbitrary types by name was indefensible, and the type filter added in the same period is the replacement story: JSON or XML plus an allow-list, rather than binary plus trust.

So the upgrade from 3.x to 4.x is not a mechanical change. If you were using BinaryFormatSerializer, you have to pick a text format and decide what types your payloads are allowed to name, and that is a design decision rather than a configuration change.

The repository carries a CHANGELOG.md, and the top level has a CLAUDE.md and a CODE_OF_CONDUCT.md alongside the src/ directory and the usual GitHub and editor folders, which is the standard shape for a library with a small, single maintainer.

## Installing it and checking the framework range first

The installation instruction is a single NuGet command in the Package Manager Console.

```powershell
Install-Package Serialize.Linq
```

The package is published as Serialize.Linq, and the README links the NuGet listing. For a project that references packages in a csproj or a PackageReference, the README does not give that form, so the console command is the documented path.

Before installing, check the supported platform list, because it is wider than most people expect and narrower than they might hope. The README lists .NET 10.0, 9.0, 8.0, 7.0 and 6.0, then .NET 4.8 and 4.8.1, then .NET Standard 2.0 and 2.1.

That combination tells you two things. The modern .NET Framework line is fully covered, and the .NET Standard targets mean the library can be consumed from .NET Framework 4.6.1 or later as well, which matters for teams with a mixed estate where a shared library has to load in a legacy application.

For the first real use, the smallest useful test is the one in the README: build an expression, serialize it to text with the JSON formatter, deserialize it back and check that the resulting tree behaves the same. If that round trip works, the format question is settled and the remaining work is deciding which types your payloads are allowed to name.

## Where Serialize.Linq is the wrong tool

Three cases, and the first one covers most of the situations people look this library up for.

If the query is known at compile time, none of this is needed. A fixed filter can be a delegate, a stored procedure with a small parameter object, or a strongly typed request DTO. Serializing an expression tree to text and sending it to a server that parses it back is strictly more work and strictly more surface area than sending the values the filter depends on. The library is for the case where the shape of the query genuinely varies at runtime, and using it for a query with two possible states means paying for a general mechanism to express a small decision.

The second is the case where the receiving side should not be executing the caller's query at all. Deserializing an expression produces a tree that can be evaluated, and if the server evaluates it against a real data source, the payload author effectively chooses the query. The type filter limits which types can be named, but it does not make an arbitrary predicate safe to run against your database, and the README does not claim otherwise. If the receiving side only needs a few extracted values, the payload should carry values rather than a program.

The third is operational. The library is a single maintainer's project with no GitHub releases, so version tracking runs through the package feed and the CHANGELOG, and the last push was on 2026-08-12. The supported platform list is broad and the API surface is small, so the risk is low, but you are depending on one person for a capability that is easy to miss when it disappears. That is a reasonable trade for an internal utility and a poor one for a public API you promise to keep stable.

## Conclusion

Adopt Serialize.Linq if you need to ship a LINQ expression to another process or store one and evaluate it later, and you control the shape of the payload, because turning an expression tree into text is the only practical way to move it. Do not adopt it if the expression is fixed and known at compile time, since a plain delegate or a stored procedure is smaller and faster, and treat a payload from an untrusted source as dangerous even with the filter in place. Verify first that the type filter covers the generic arguments your expressions actually use, and read the 4.0 breaking change before upgrading, since BinaryFormatSerializer was removed outright.

## FAQ

### What is Serialize.Linq used for?

It serializes and deserializes LINQ expressions so they can be sent over the wire or persisted in a database. The library supports XML, JSON and binary formats and a range of expression types including binary, unary, member access and lambda.

### How do I install Serialize.Linq?

Install it from NuGet with the Package Manager Console command Install-Package Serialize.Linq. The README links the package listing at nuget.org/packages/Serialize.Linq.

### How do I safely deserialize an expression from an untrusted source?

Supply an ITypeFilter on an ExpressionContext to allow-list the permitted types, for example an AllowedTypesFilter with explicit types and an allowed namespace. Any type the filter rejects throws a TypeNotAllowedException, and the filter is checked for every resolved type including generic arguments.

### What changed in version 4.0 of Serialize.Linq?

BinaryFormatSerializer was removed outright because of security concerns with BinaryFormatter, and the README links the BinaryFormatter Security Guide for details. The change is a breaking one rather than a deprecation, so code using that serializer has to move to a text format.

### Which .NET versions does Serialize.Linq support?

The README lists .NET 10.0, 9.0, 8.0, 7.0 and 6.0, .NET Framework 4.8 and 4.8.1, and .NET Standard 2.0 and 2.1, which lets the library be consumed from older .NET Framework applications as well as current .NET.

## Sources

- [esskar/Serialize.Linq on GitHub](https://github.com/esskar/Serialize.Linq)
- [Issues](https://github.com/esskar/Serialize.Linq/issues)
- [License: MIT](https://github.com/esskar/Serialize.Linq/blob/main/LICENSE)
- [README](https://github.com/esskar/Serialize.Linq/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/esskar-serialize-linq
