| name | dotnet-serialization |
| description | Serializes data. System.Text.Json source generators, Protobuf, MessagePack, AOT-safe patterns. |
| metadata | {"short-description":".NET skill guidance for csharp tasks"} |
dotnet-serialization
AOT-friendly serialization patterns for .NET applications. Covers System.Text.Json source generators for compile-time
serialization, Protocol Buffers (Protobuf) for efficient binary serialization, and MessagePack for high-performance
compact binary format. Includes performance tradeoff guidance for choosing the right serializer and warnings about
reflection-based serialization in AOT scenarios.
Scope
- System.Text.Json source generators for compile-time serialization
- Protocol Buffers (Protobuf) for binary serialization
- MessagePack for high-performance compact format
- Performance tradeoff guidance for serializer selection
- AOT-safe serialization patterns and anti-patterns
Out of scope
- Source generator authoring patterns -- see [skill:dotnet-csharp-source-generators]
- HTTP client factory and resilience pipelines -- see [skill:dotnet-http-client] and [skill:dotnet-resilience]
- Native AOT architecture and trimming -- see [skill:dotnet-native-aot] and [skill:dotnet-trimming]
Cross-references: [skill:dotnet-csharp-source-generators] for understanding how STJ source generators work under the
hood. See [skill:dotnet-integration-testing] for testing serialization round-trip correctness.
Serialization Format Comparison
| Format | Library | AOT-Safe | Human-Readable | Relative Size | Relative Speed | Best For |
|---|
| JSON | System.Text.Json (source gen) | Yes | Yes | Largest | Good | APIs, config, web clients |
| Protobuf | Google.Protobuf | Yes | No | Smallest | Fastest | Service-to-service, gRPC wire format |
| MessagePack | MessagePack-CSharp | Yes (with AOT resolver) | No | Small | Fast | High-throughput caching, real-time |
| JSON | Newtonsoft.Json | No (reflection) | Yes | Largest | Slower | Legacy only -- do not use for AOT |
When to Choose What
- System.Text.Json with source generators: Default choice for APIs, configuration, and any scenario where
human-readable output or web client consumption matters. AOT-safe when using source generators.
- Protobuf: Default wire format for gRPC. Best throughput and smallest payload size for service-to-service
communication. Schema-first development with
.proto files.
- MessagePack: When you need binary compactness without
.proto schema management. Good for caching layers,
real-time messaging, and high-throughput scenarios where schema evolution is managed via attributes.
System.Text.Json Source Generators
System.Text.Json source generators produce compile-time serialization code, eliminating runtime reflection. This is
required for Native AOT and strongly recommended for all new projects. See [skill:dotnet-csharp-source-generators]
for the underlying incremental generator mechanics.
Basic Setup
Define a JsonSerializerContext with [JsonSerializable] attributes for each type you serialize:
using System.Text.Json.Serialization;
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
[JsonSerializable(typeof(OrderStatus))]
public partial class AppJsonContext : JsonSerializerContext
{
}
```json
### Using the Generated Context
```csharp
string json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
Order? result = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
TypeInfoResolver = AppJsonContext.Default
};
string json = JsonSerializer.Serialize(order, options);
```json
### ASP.NET Core Integration
Register the source-generated context so Minimal APIs use it automatically. Note that `ConfigureHttpJsonOptions` applies to Minimal APIs only -- MVC controllers require separate configuration via `AddJsonOptions`:
```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
var app = builder.Build();
app.MapGet("/orders/{id}", async (int id, OrderService service) =>
{
var order = await service.GetAsync(id);
return order ? Results.Ok(order) : Results.NotFound();
});
app.MapPost(, (Order order, OrderService service) =>
{
service.CreateAsync(order);
Results.Created(, order);
});
```text
;
});
```json
```csharp
[]
[]
[]
:
{
}
```json
```csharp
[]
[]
[]
{
Amount { ; ; }
Currency { ; ; } = ;
}
:
{
Last4Digits { ; ; } = ;
}
[]
:
{
}
```json
---
Protocol Buffers provide schema-first binary serialization. Protobuf the wire format gRPC AOT-safe.
```xml
<PackageReference Include= Version= />
<PackageReference Include= Version= PrivateAssets= />
```xml
```protobuf
syntax = ;
import ;
option csharp_namespace = ;
message OrderMessage {
int32 id = ;
customer_id = ;
repeated OrderItemMessage items = ;
google.protobuf.Timestamp created_at = ;
}
message OrderItemMessage {
product_id = ;
int32 quantity = ;
unit_price = ;
}
```text
Use Protobuf binary serialization without gRPC you need compact payloads caching, messaging, storage:
```csharp
Google.Protobuf;
[] bytes = order.ToByteArray();
restored = OrderMessage.Parser.ParseFrom(bytes);
stream = File.OpenWrite();
order.WriteTo(stream);
```text
```xml
<ItemGroup>
<Protobuf Include= GrpcServices= />
</ItemGroup>
```xml
---
MessagePack-CSharp provides high-performance binary serialization smaller payloads than JSON good .NET integration.
```xml
<PackageReference Include= Version= />
<!-- For AOT support -->
<PackageReference Include= Version= />
```xml
```csharp
MessagePack;
[]
{
[]
Id { ; ; }
[]
CustomerId { ; ; } = ;
[]
List<OrderItem> Items { ; ; } = [];
[]
DateTimeOffset CreatedAt { ; ; }
}
```text
```csharp
[] bytes = MessagePackSerializer.Serialize(order);
restored = MessagePackSerializer.Deserialize<Order>(bytes);
lz4Options = MessagePackSerializerOptions.Standard.WithCompression(
MessagePackCompression.Lz4BlockArray);
[] compressed = MessagePackSerializer.Serialize(order, lz4Options);
```text
For Native AOT compatibility, use the MessagePack source generator to produce a resolver:
```csharp
MessagePackSerializer.DefaultOptions = MessagePackSerializerOptions.Standard
.WithResolver(GeneratedResolver.Instance);
```text
---
**Do use reflection-based serializers Native AOT trimming scenarios.** Reflection-based serialization fails at runtime the linker removes type metadata.
Newtonsoft.Json (`JsonConvert.SerializeObject` / `JsonConvert.DeserializeObject`) relies heavily runtime reflection. It **incompatible** Native AOT trimming:
```csharp
json = JsonConvert.SerializeObject(order);
order = JsonConvert.DeserializeObject<Order>(json);
json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);
```json
Even System.Text.Json falls back to reflection without a source-generated context:
```csharp
json = JsonSerializer.Serialize(order);
json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
```json
Replace `JsonConvert.SerializeObject` / `DeserializeObject` `JsonSerializer.Serialize` / `Deserialize`
Replace `[JsonProperty]` `[JsonPropertyName]`
Replace `JsonConverter` `<>`
4. `` `[]`
5. `` / `` `` / `` -
6. - --
---
##
### ()
| | (/) | (/) | |
|--------|--------------------|-----------------------|-------------|
| | | | |
| | | | |
| | | | () |
| | | | () |
| | | | () |
###
- ** ``** -- ; create once reuse
- **Use `JsonSerializerContext`** -- eliminates warm-up cost reduces allocation
- **Use `Utf8JsonWriter` / `Utf8JsonReader`** streaming scenarios you process JSON without full materialization
- **Use Protobuf `ByteString`** binary data instead of base64-encoded strings JSON
- **Enable MessagePack LZ4 compression** large payloads over the wire
---
- **Default to System.Text.Json source generators** all JSON serialization -- it AOT-safe, fast, built the framework
- **Use Protobuf service-to-service binary serialization** -- especially the wire format gRPC
- **Use MessagePack high-throughput caching real-time** -- binary compactness matters but `.proto` schema management unwanted
- **Never use Newtonsoft.Json AOT-targeted projects** -- it reflection-based incompatible trimming
- **Always register `JsonSerializerContext` ASP.NET Core** -- use `ConfigureHttpJsonOptions` Minimal APIs `AddJsonOptions` ; MessagePack requires `[MessagePackObject]`
See [skill:dotnet-native-aot] comprehensive AOT compilation pipeline, [skill:dotnet-aot-architecture] AOT-first design patterns, [skill:dotnet-trimming] trimming strategies ILLink descriptor configuration.
---
**Do use `JsonSerializer.Serialize(obj)` without a context AOT projects** -- it falls back to reflection fails at runtime. Always pass the source-generated `TypeInfo`.
**Do forget to list collection types `[JsonSerializable]`** -- `[JsonSerializable((Order))]` does cover `List<Order>`. Add `[JsonSerializable((List<Order>))]` separately.
**Do use Newtonsoft.Json `[JsonProperty]` attributes System.Text.Json** -- they are silently ignored. Use `[JsonPropertyName]` instead.
**Do mix MessagePack `[Key]` integer keys `[Key]` keys** the same type hierarchy -- pick one strategy stay consistent.
**Do omit `GrpcServices` attribute `<Protobuf>` items** -- without it, both client server stubs are generated, which may cause build errors you only need one.
---
- [System.Text.Json source generation](https:
- [Migrate Newtonsoft.Json to System.Text.Json](https:
- [Protocol Buffers .NET](https:
- [MessagePack-CSharp](https:
- [Native AOT deployment](https:
Code Navigation (Serena MCP)
Primary approach: Use Serena symbol operations for efficient code navigation:
- Find definitions:
serena_find_symbol instead of text search
- Understand structure:
serena_get_symbols_overview for file organization
- Track references:
serena_find_referencing_symbols for impact analysis
- Precise edits:
serena_replace_symbol_body for clean modifications
When to use Serena vs traditional tools:
- Use Serena: Navigation, refactoring, dependency analysis, precise edits
- Use Read/Grep: Reading full files, pattern matching, simple text operations
- Fallback: If Serena unavailable, traditional tools work fine
Example workflow:
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"