$npx -y skills add Aaronontheweb/dotnet-skills --skill serializationChoose 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.
| 1 | # Serialization in .NET |
| 2 | |
| 3 | ## When to Use This Skill |
| 4 | |
| 5 | Use this skill when: |
| 6 | - Choosing a serialization format for APIs, messaging, or persistence |
| 7 | - Migrating from Newtonsoft.Json to System.Text.Json |
| 8 | - Implementing AOT-compatible serialization |
| 9 | - Designing wire formats for distributed systems |
| 10 | - Optimizing serialization performance |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## Schema-Based vs Reflection-Based |
| 15 | |
| 16 | | Aspect | Schema-Based | Reflection-Based | |
| 17 | |--------|--------------|------------------| |
| 18 | | **Examples** | Protobuf, MessagePack, System.Text.Json (source gen) | Newtonsoft.Json, BinaryFormatter | |
| 19 | | **Type info in payload** | No (external schema) | Yes (type names embedded) | |
| 20 | | **Versioning** | Explicit field numbers/names | Implicit (type structure) | |
| 21 | | **Performance** | Fast (no reflection) | Slower (runtime reflection) | |
| 22 | | **AOT compatible** | Yes | No | |
| 23 | | **Wire compatibility** | Excellent | Poor | |
| 24 | |
| 25 | **Recommendation**: Use schema-based serialization for anything that crosses process boundaries. |
| 26 | |
| 27 | --- |
| 28 | |
| 29 | ## Format Recommendations |
| 30 | |
| 31 | | Use Case | Recommended Format | Why | |
| 32 | |----------|-------------------|-----| |
| 33 | | **REST APIs** | System.Text.Json (source gen) | Standard, AOT-compatible | |
| 34 | | **gRPC** | Protocol Buffers | Native format, excellent versioning | |
| 35 | | **Actor messaging** | MessagePack or Protobuf | Compact, fast, version-safe | |
| 36 | | **Event sourcing** | Protobuf or MessagePack | Must handle old events forever | |
| 37 | | **Caching** | MessagePack | Compact, fast | |
| 38 | | **Configuration** | JSON (System.Text.Json) | Human-readable | |
| 39 | | **Logging** | JSON (System.Text.Json) | Structured, parseable | |
| 40 | |
| 41 | ### Formats to Avoid |
| 42 | |
| 43 | | Format | Problem | |
| 44 | |--------|---------| |
| 45 | | **BinaryFormatter** | Security vulnerabilities, deprecated, never use | |
| 46 | | **Newtonsoft.Json default** | Type names in payload break on rename | |
| 47 | | **DataContractSerializer** | Complex, poor versioning | |
| 48 | | **XML** | Verbose, slow, complex | |
| 49 | |
| 50 | --- |
| 51 | |
| 52 | ## System.Text.Json with Source Generators |
| 53 | |
| 54 | For JSON serialization, use System.Text.Json with source generators for AOT compatibility and performance. |
| 55 | |
| 56 | ### Setup |
| 57 | |
| 58 | ```csharp |
| 59 | // Define a JsonSerializerContext with all your types |
| 60 | [JsonSerializable(typeof(Order))] |
| 61 | [JsonSerializable(typeof(OrderItem))] |
| 62 | [JsonSerializable(typeof(Customer))] |
| 63 | [JsonSerializable(typeof(List<Order>))] |
| 64 | [JsonSourceGenerationOptions( |
| 65 | PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, |
| 66 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] |
| 67 | public partial class AppJsonContext : JsonSerializerContext { } |
| 68 | ``` |
| 69 | |
| 70 | ### Usage |
| 71 | |
| 72 | ```csharp |
| 73 | // Serialize with context |
| 74 | var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order); |
| 75 | |
| 76 | // Deserialize with context |
| 77 | var order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order); |
| 78 | |
| 79 | // Configure in ASP.NET Core |
| 80 | builder.Services.ConfigureHttpJsonOptions(options => |
| 81 | { |
| 82 | options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default); |
| 83 | }); |
| 84 | ``` |
| 85 | |
| 86 | ### Benefits |
| 87 | |
| 88 | - **No reflection at runtime** - All type info generated at compile time |
| 89 | - **AOT compatible** - Works with Native AOT publishing |
| 90 | - **Faster** - No runtime type analysis |
| 91 | - **Trim-safe** - Linker knows exactly what's needed |
| 92 | |
| 93 | --- |
| 94 | |
| 95 | ## Protocol Buffers (Protobuf) |
| 96 | |
| 97 | Best for: Actor systems, gRPC, event sourcing, any long-lived wire format. |
| 98 | |
| 99 | ### Setup |
| 100 | |
| 101 | ```bash |
| 102 | dotnet add package Google.Protobuf |
| 103 | dotnet add package Grpc.Tools |
| 104 | ``` |
| 105 | |
| 106 | ### Define Schema |
| 107 | |
| 108 | ```protobuf |
| 109 | // orders.proto |
| 110 | syntax = "proto3"; |
| 111 | |
| 112 | message Order { |
| 113 | string id = 1; |
| 114 | string customer_id = 2; |
| 115 | repeated OrderItem items = 3; |
| 116 | int64 created_at_ticks = 4; |
| 117 | |
| 118 | // Adding new fields is always safe |
| 119 | string notes = 5; // Added in v2 - old readers ignore it |
| 120 | } |
| 121 | |
| 122 | message OrderItem { |
| 123 | string product_id = 1; |
| 124 | int32 quantity = 2; |
| 125 | int64 price_cents = 3; |
| 126 | } |
| 127 | ``` |
| 128 | |
| 129 | ### Versioning Rules |
| 130 | |
| 131 | ```protobuf |
| 132 | // SAFE: Add new fields with new numbers |
| 133 | message Order { |
| 134 | string id = 1; |
| 135 | string customer_id = 2; |
| 136 | string shipping_address = 5; // NEW - safe |
| 137 | } |
| 138 | |
| 139 | // SAFE: Remove fields (old readers ignore unknown, new readers use default) |
| 140 | // Just stop using the field, keep the number reserved |
| 141 | message Order { |
| 142 | string id = 1; |
| 143 | // customer_id removed, but field 2 is reserved |
| 144 | reserved 2; |
| 145 | } |
| 146 | |
| 147 | // UNSAFE: Change field types |
| 148 | message Order { |
| 149 | int32 id = 1; // Was: string - BREAKS! |
| 150 | } |
| 151 | |
| 152 | // UNSAFE: Reuse field numbers |
| 153 | message Order { |
| 154 | reserved 2; |
| 155 | string new_field = 2; // Reusing 2 - BREAKS! |
| 156 | } |
| 157 | ``` |
| 158 | |
| 159 | --- |
| 160 | |
| 161 | ## MessagePack |
| 162 | |
| 163 | Best for: High-performance scenarios, compact payloads, actor messaging. |
| 164 | |
| 165 | ### Setup |
| 166 | |
| 167 | ```bash |
| 168 | dotnet add package MessagePack |
| 169 | dotnet add package MessagePack.Annotations |
| 170 | ``` |
| 171 | |
| 172 | ### Usage with Contracts |
| 173 | |
| 174 | ```csharp |
| 175 | [MessagePackObject] |
| 176 | public sealed class Order |
| 177 | { |
| 178 | [Key(0)] |
| 179 | public required string Id { get; init; } |