Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
aaronontheweb avatar

Serialization

  • 423 installs
  • 1.1k repo stars
  • Updated July 3, 2026
  • aaronontheweb/dotnet-skills

serialization is a .NET & C# Claude Code skill that guides developers through choosing and configuring JSON or binary serializers for APIs, messaging, and persistence when contracts, performance, or polymorphic payloads

About

serialization is a Claude Code skill from the Aaronontheweb/dotnet-skills plugin that guides C# developers through choosing and configuring JSON or binary serializers for .NET APIs, messaging, and persistence. The skill compares schema-based formats—Protocol Buffers, MessagePack, and System.Text.Json with AOT source generators—against reflection-based options like Newtonsoft.Json, with explicit guidance on when each fits REST APIs, gRPC, actor systems, event sourcing, and caching. It includes ready-to-adapt code for JsonSerializerContext, .proto schemas, MessagePack contracts, ASP.NET Core HttpJsonOptions, and a six-row Newtonsoft-to-System.Text.Json migration table covering polymorphism with JsonDerivedType. Developers reach for serialization when migrating off Newtonsoft.Json, publishing Native AOT binaries, designing wire-compatible distributed payloads, or optimizing hot-path serialize/deserialize throughput across five compared formats. The skill is non-invocable guidance (invocable: false) within a 30-skill, 5-agent dotnet-skills library built for production .NET patterns.

  • System.Text.Json configuration
  • Source generator patterns
  • Polymorphic type handling
  • Performance vs compatibility tradeoffs
  • Contract versioning practices

Serialization by the numbers

  • 423 all-time installs (skills.sh)
  • Ranked #45 of 153 .NET & C# skills by installs in the Skillselion catalog
  • Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aaronontheweb/dotnet-skills --skill serialization

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs423
repo stars1.1k
Last updatedJuly 3, 2026
Repositoryaaronontheweb/dotnet-skills

Which .NET serializer should REST APIs use?

Choose and configure JSON or binary serializers for .NET APIs and messaging when contracts, performance, or polymorphic payloads need consistent handling.

Who is it for?

Developers building .NET REST APIs, gRPC services, or Akka.NET actor systems who need AOT-safe, versioned wire formats instead of reflection-heavy JSON defaults.

Skip if: Developers already standardized on a single serializer with no migration, AOT, or cross-service compatibility requirements should skip this decision guide.

When should I use this skill?

Developer asks about .NET serialization format choice, Newtonsoft.Json migration, System.Text.Json source generators, Protobuf or MessagePack setup, or AOT-compatible wire formats.

What you get

JsonSerializerContext source generators, .proto schemas, MessagePack contracts, ASP.NET Core HttpJsonOptions configuration, and Newtonsoft.Json migration mappings

  • JsonSerializerContext source generator classes
  • .proto schema files
  • MessagePack contract annotations

By the numbers

  • Part of the dotnet-skills plugin with 30 skills and 5 specialized agents
  • Compares serialize and deserialize throughput across 5 serialization formats
  • Documents 6 Newtonsoft.Json to System.Text.Json migration mappings

Files

SKILL.mdMarkdownGitHub ↗

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

AspectSchema-BasedReflection-Based
ExamplesProtobuf, MessagePack, System.Text.Json (source gen)Newtonsoft.Json, BinaryFormatter
Type info in payloadNo (external schema)Yes (type names embedded)
VersioningExplicit field numbers/namesImplicit (type structure)
PerformanceFast (no reflection)Slower (runtime reflection)
AOT compatibleYesNo
Wire compatibilityExcellentPoor

Recommendation: Use schema-based serialization for anything that crosses process boundaries.

---

Format Recommendations

Use CaseRecommended FormatWhy
REST APIsSystem.Text.Json (source gen)Standard, AOT-compatible
gRPCProtocol BuffersNative format, excellent versioning
Actor messagingMessagePack or ProtobufCompact, fast, version-safe
Event sourcingProtobuf or MessagePackMust handle old events forever
CachingMessagePackCompact, fast
ConfigurationJSON (System.Text.Json)Human-readable
LoggingJSON (System.Text.Json)Structured, parseable

Formats to Avoid

FormatProblem
BinaryFormatterSecurity vulnerabilities, deprecated, never use
Newtonsoft.Json defaultType names in payload break on rename
DataContractSerializerComplex, poor versioning
XMLVerbose, slow, complex

---

System.Text.Json with Source Generators

For JSON serialization, use System.Text.Json with source generators for AOT compatibility and performance.

Setup

// 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

// 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

dotnet add package Google.Protobuf
dotnet add package Grpc.Tools

Define Schema

// 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

// 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

dotnet add package MessagePack
dotnet add package MessagePack.Annotations

Usage with Contracts

[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

// 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

NewtonsoftSystem.Text.JsonFix
$type in JSONNot supported by defaultUse discriminators or custom converters
JsonPropertyJsonPropertyNameDifferent attribute
DefaultValueHandlingDefaultIgnoreConditionDifferent API
NullValueHandlingDefaultIgnoreConditionDifferent API
Private settersRequires [JsonInclude]Explicit opt-in
Polymorphism[JsonDerivedType] (.NET 7+)Explicit discriminators

Migration Pattern

// 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

// .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:

// 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:

// 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

// 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):

FormatSerializeDeserializeSize
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:

akka {
  actor {
    serializers {
      messagepack = "Akka.Serialization.MessagePackSerializer, Akka.Serialization.MessagePack"
    }
    serialization-bindings {
      "MyApp.Messages.IMessage, MyApp" = messagepack
    }
  }
}

See Akka.NET Serialization Docs.

---

Best Practices

DO

// 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

// 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.html

Related skills

How it compares

Pick serialization over generic JSON advice when you need format-specific decision tables, AOT source generator setup, or cross-service wire compatibility patterns for .NET distributed systems.

FAQ

When should .NET APIs use Protobuf instead of JSON?

The serialization skill recommends Protocol Buffers for gRPC, actor messaging, and event sourcing where compact payloads, explicit field-number versioning, and long-lived wire compatibility matter more than human-readable JSON.

How does serialization handle Newtonsoft.Json migration?

The serialization skill maps six common Newtonsoft.Json patterns—including JsonProperty, null handling, and polymorphism—to System.Text.Json equivalents using JsonPropertyName, DefaultIgnoreCondition, and JsonDerivedType discriminators in .NET 7+.

Which .NET serializers support Native AOT publishing?

The serialization skill designates System.Text.Json with source generators, Protocol Buffers, and MessagePack with generated resolvers as AOT-compatible, and flags reflection-based Newtonsoft.Json and deprecated BinaryFormatter as incompatible or unsafe.

.NET & C#backendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.