serialization
Choose the right serialization format for .NET applications. Prefer schema-based formats (Protobuf, MessagePack) over reflection-based (Newtonsoft.Json). Use System.Text.Json with AOT source generators for JSON scenarios.
Best use case
serialization is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Choose the right serialization format for .NET applications. Prefer schema-based formats (Protobuf, MessagePack) over reflection-based (Newtonsoft.Json). Use System.Text.Json with AOT source generators for JSON scenarios.
Teams using serialization should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/serialization/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How serialization Compares
| Feature / Agent | serialization | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Choose the right serialization format for .NET applications. Prefer schema-based formats (Protobuf, MessagePack) over reflection-based (Newtonsoft.Json). Use System.Text.Json with AOT source generators for JSON scenarios.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Serialization in .NET
## When to Use This Skill
Use this skill when:
- Choosing a serialization format for APIs, messaging, or persistence
- Migrating from Newtonsoft.Json to System.Text.Json
- Implementing AOT-compatible serialization
- Designing wire formats for distributed systems
- Optimizing serialization performance
---
## Schema-Based vs Reflection-Based
| Aspect | Schema-Based | Reflection-Based |
|--------|--------------|------------------|
| **Examples** | Protobuf, MessagePack, System.Text.Json (source gen) | Newtonsoft.Json, BinaryFormatter |
| **Type info in payload** | No (external schema) | Yes (type names embedded) |
| **Versioning** | Explicit field numbers/names | Implicit (type structure) |
| **Performance** | Fast (no reflection) | Slower (runtime reflection) |
| **AOT compatible** | Yes | No |
| **Wire compatibility** | Excellent | Poor |
**Recommendation**: Use schema-based serialization for anything that crosses process boundaries.
---
## Format Recommendations
| Use Case | Recommended Format | Why |
|----------|-------------------|-----|
| **REST APIs** | System.Text.Json (source gen) | Standard, AOT-compatible |
| **gRPC** | Protocol Buffers | Native format, excellent versioning |
| **Actor messaging** | MessagePack or Protobuf | Compact, fast, version-safe |
| **Event sourcing** | Protobuf or MessagePack | Must handle old events forever |
| **Caching** | MessagePack | Compact, fast |
| **Configuration** | JSON (System.Text.Json) | Human-readable |
| **Logging** | JSON (System.Text.Json) | Structured, parseable |
### Formats to Avoid
| Format | Problem |
|--------|---------|
| **BinaryFormatter** | Security vulnerabilities, deprecated, never use |
| **Newtonsoft.Json default** | Type names in payload break on rename |
| **DataContractSerializer** | Complex, poor versioning |
| **XML** | Verbose, slow, complex |
---
## System.Text.Json with Source Generators
For JSON serialization, use System.Text.Json with source generators for AOT compatibility and performance.
### Setup
```csharp
// Define a JsonSerializerContext with all your types
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(OrderItem))]
[JsonSerializable(typeof(Customer))]
[JsonSerializable(typeof(List<Order>))]
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
public partial class AppJsonContext : JsonSerializerContext { }
```
### Usage
```csharp
// Serialize with context
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
// Deserialize with context
var order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);
// Configure in ASP.NET Core
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
```
### Benefits
- **No reflection at runtime** - All type info generated at compile time
- **AOT compatible** - Works with Native AOT publishing
- **Faster** - No runtime type analysis
- **Trim-safe** - Linker knows exactly what's needed
---
## Protocol Buffers (Protobuf)
Best for: Actor systems, gRPC, event sourcing, any long-lived wire format.
### Setup
```bash
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools
```
### Define Schema
```protobuf
// orders.proto
syntax = "proto3";
message Order {
string id = 1;
string customer_id = 2;
repeated OrderItem items = 3;
int64 created_at_ticks = 4;
// Adding new fields is always safe
string notes = 5; // Added in v2 - old readers ignore it
}
message OrderItem {
string product_id = 1;
int32 quantity = 2;
int64 price_cents = 3;
}
```
### Versioning Rules
```protobuf
// SAFE: Add new fields with new numbers
message Order {
string id = 1;
string customer_id = 2;
string shipping_address = 5; // NEW - safe
}
// SAFE: Remove fields (old readers ignore unknown, new readers use default)
// Just stop using the field, keep the number reserved
message Order {
string id = 1;
// customer_id removed, but field 2 is reserved
reserved 2;
}
// UNSAFE: Change field types
message Order {
int32 id = 1; // Was: string - BREAKS!
}
// UNSAFE: Reuse field numbers
message Order {
reserved 2;
string new_field = 2; // Reusing 2 - BREAKS!
}
```
---
## MessagePack
Best for: High-performance scenarios, compact payloads, actor messaging.
### Setup
```bash
dotnet add package MessagePack
dotnet add package MessagePack.Annotations
```
### Usage with Contracts
```csharp
[MessagePackObject]
public sealed class Order
{
[Key(0)]
public required string Id { get; init; }
[Key(1)]
public required string CustomerId { get; init; }
[Key(2)]
public required IReadOnlyList<OrderItem> Items { get; init; }
[Key(3)]
public required DateTimeOffset CreatedAt { get; init; }
// New field - old readers skip unknown keys
[Key(4)]
public string? Notes { get; init; }
}
// Serialize
var bytes = MessagePackSerializer.Serialize(order);
// Deserialize
var order = MessagePackSerializer.Deserialize<Order>(bytes);
```
### AOT-Compatible Setup
```csharp
// Use source generator for AOT
[MessagePackObject]
public partial class Order { } // partial enables source gen
// Configure resolver
var options = MessagePackSerializerOptions.Standard
.WithResolver(CompositeResolver.Create(
GeneratedResolver.Instance, // Generated
StandardResolver.Instance));
```
---
## Migrating from Newtonsoft.Json
### Common Issues
| Newtonsoft | System.Text.Json | Fix |
|------------|------------------|-----|
| `$type` in JSON | Not supported by default | Use discriminators or custom converters |
| `JsonProperty` | `JsonPropertyName` | Different attribute |
| `DefaultValueHandling` | `DefaultIgnoreCondition` | Different API |
| `NullValueHandling` | `DefaultIgnoreCondition` | Different API |
| Private setters | Requires `[JsonInclude]` | Explicit opt-in |
| Polymorphism | `[JsonDerivedType]` (.NET 7+) | Explicit discriminators |
### Migration Pattern
```csharp
// Newtonsoft (reflection-based)
public class Order
{
[JsonProperty("order_id")]
public string Id { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string? Notes { get; set; }
}
// System.Text.Json (source-gen compatible)
public sealed record Order(
[property: JsonPropertyName("order_id")]
string Id,
string? Notes // Null handling via JsonSerializerOptions
);
[JsonSerializable(typeof(Order))]
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
public partial class OrderJsonContext : JsonSerializerContext { }
```
### Polymorphism with Discriminators
```csharp
// .NET 7+ polymorphism
[JsonDerivedType(typeof(CreditCardPayment), "credit_card")]
[JsonDerivedType(typeof(BankTransferPayment), "bank_transfer")]
public abstract record Payment(decimal Amount);
public sealed record CreditCardPayment(decimal Amount, string Last4) : Payment(Amount);
public sealed record BankTransferPayment(decimal Amount, string AccountNumber) : Payment(Amount);
// Serializes as:
// { "$type": "credit_card", "amount": 100, "last4": "1234" }
```
---
## Wire Compatibility Patterns
### Tolerant Reader
Old code must safely ignore unknown fields:
```csharp
// Protobuf/MessagePack: Automatic - unknown fields skipped
// System.Text.Json: Configure to allow
var options = new JsonSerializerOptions
{
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip
};
```
### Introduce Read Before Write
Deploy deserializers before serializers for new formats:
```csharp
// Phase 1: Add deserializer (deployed everywhere)
public Order Deserialize(byte[] data, string manifest) => manifest switch
{
"Order.V1" => DeserializeV1(data),
"Order.V2" => DeserializeV2(data), // NEW - can read V2
_ => throw new NotSupportedException()
};
// Phase 2: Enable serializer (next release, after V1 deployed everywhere)
public (byte[] data, string manifest) Serialize(Order order) =>
_useV2Format
? (SerializeV2(order), "Order.V2")
: (SerializeV1(order), "Order.V1");
```
### Never Embed Type Names
```csharp
// BAD: Type name in payload - renaming class breaks wire format
{
"$type": "MyApp.Order, MyApp.Core",
"id": "123"
}
// GOOD: Explicit discriminator - refactoring safe
{
"type": "order",
"id": "123"
}
```
---
## Performance Comparison
Approximate throughput (higher is better):
| Format | Serialize | Deserialize | Size |
|--------|-----------|-------------|------|
| MessagePack | ★★★★★ | ★★★★★ | ★★★★★ |
| Protobuf | ★★★★★ | ★★★★★ | ★★★★★ |
| System.Text.Json (source gen) | ★★★★☆ | ★★★★☆ | ★★★☆☆ |
| System.Text.Json (reflection) | ★★★☆☆ | ★★★☆☆ | ★★★☆☆ |
| Newtonsoft.Json | ★★☆☆☆ | ★★☆☆☆ | ★★★☆☆ |
For hot paths, prefer MessagePack or Protobuf.
---
## Akka.NET Serialization
For Akka.NET actor systems, use schema-based serialization:
```hocon
akka {
actor {
serializers {
messagepack = "Akka.Serialization.MessagePackSerializer, Akka.Serialization.MessagePack"
}
serialization-bindings {
"MyApp.Messages.IMessage, MyApp" = messagepack
}
}
}
```
See [Akka.NET Serialization Docs](https://getakka.net/articles/networking/serialization.html).
---
## Best Practices
### DO
```csharp
// Use source generators for System.Text.Json
[JsonSerializable(typeof(Order))]
public partial class AppJsonContext : JsonSerializerContext { }
// Use explicit field numbers/keys
[MessagePackObject]
public class Order
{
[Key(0)] public string Id { get; init; }
}
// Use records for immutable message types
public sealed record OrderCreated(OrderId Id, CustomerId CustomerId);
```
### DON'T
```csharp
// Don't use BinaryFormatter (ever)
var formatter = new BinaryFormatter(); // Security risk!
// Don't embed type names in wire format
settings.TypeNameHandling = TypeNameHandling.All; // Breaks on rename!
// Don't use reflection serialization for hot paths
JsonConvert.SerializeObject(order); // Slow, not AOT-compatible
```
---
## Resources
- **System.Text.Json Source Generation**: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation
- **Protocol Buffers**: https://protobuf.dev/
- **MessagePack-CSharp**: https://github.com/MessagePack-CSharp/MessagePack-CSharp
- **Akka.NET Serialization**: https://getakka.net/articles/networking/serialization.html
- **Wire Compatibility**: https://getakka.net/community/contributing/wire-compatibility.htmlRelated Skills
symfony:api-platform-serialization
Use when symfony api platform serialization
bgo
Automated Blender build-go workflow. Automatically builds, removes old version, installs, enables, and launches Blender with your extension/add-on. Use when you want to quickly test changes, execute complete build-to-launch cycle, or run custom packaging scripts with automatic Blender launch.
maintenance
Cleans up and organizes project files. Use when user mentions '整理', 'cleanup', 'アーカイブ', 'archive', '肥大化', 'Plans.md', 'session-log', or asks to clean up old tasks, archive completed items, or organize files. Do NOT load for: 実装作業, レビュー, 新機能開発, デプロイ.
hello-skill
每次对话开始时,声明"[Skills✏️已加载]"
zylvie-automation
Automate Zylvie tasks via Rube MCP (Composio). Always search tools first for current schemas.
zoominfo-automation
Automate Zoominfo tasks via Rube MCP (Composio). Always search tools first for current schemas.
zoho-invoice-automation
Automate Zoho Invoice tasks via Rube MCP (Composio): invoices, estimates, expenses, clients, and payment tracking. Always search tools first for current schemas.
zoho-inventory-automation
Automate Zoho Inventory tasks via Rube MCP (Composio): items, orders, warehouses, shipments, and stock management. Always search tools first for current schemas.
zoho-bigin-automation
Automate Zoho Bigin tasks via Rube MCP (Composio): pipelines, contacts, companies, products, and small business CRM. Always search tools first for current schemas.
zoho_desk-automation
Zoho Desk automation via Rube MCP -- toolkit not currently available in Composio; no ZOHO_DESK_ tools found
zoho-automation
Automate Zoho tasks via Rube MCP (Composio). Always search tools first for current schemas.
zeroclaw
Comprehensive operational knowledge for ZeroClaw, the fast, small, fully autonomous AI assistant infrastructure built in Rust. Covers CLI, 30 providers, 14 channels, config, hardware, deployment, and security.