serialization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Serialization in .NET

.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

在以下场景使用此技能:
  • 为API、消息传递或持久化选择序列化格式
  • 从Newtonsoft.Json迁移到System.Text.Json
  • 实现兼容AOT的序列化
  • 为分布式系统设计有线格式
  • 优化序列化性能

Schema-Based vs Reflection-Based

基于Schema vs 基于反射

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.

方面基于Schema的基于反射的
示例Protobuf, MessagePack, System.Text.Json (源代码生成)Newtonsoft.Json, BinaryFormatter
负载中的类型信息无(外部Schema)有(嵌入类型名称)
版本控制显式字段编号/名称隐式(类型结构)
性能快速(无反射)较慢(运行时反射)
AOT兼容性
有线兼容性优秀较差
推荐:任何跨进程边界的场景都使用基于Schema的序列化。

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
使用场景推荐格式原因
REST APISystem.Text.Json (源代码生成)标准化、兼容AOT
gRPCProtocol Buffers原生格式、版本控制能力优秀
Actor消息传递MessagePack 或 Protobuf紧凑、快速、版本安全
事件溯源Protobuf 或 MessagePack必须永久处理旧事件
缓存MessagePack紧凑、快速
配置JSON (System.Text.Json)人类可读
日志JSON (System.Text.Json)结构化、可解析

Formats to Avoid

应避免的格式

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

格式问题
BinaryFormatter存在安全漏洞、已被弃用,绝对不要使用
Newtonsoft.Json 默认配置负载中的类型名称会在重命名时导致失效
DataContractSerializer复杂、版本控制能力差
XML冗长、缓慢、复杂

System.Text.Json with Source Generators

搭配源代码生成器的System.Text.Json

For JSON serialization, use System.Text.Json with source generators for AOT compatibility and performance.
对于JSON序列化,使用搭配源代码生成器的System.Text.Json以实现AOT兼容性和高性能。

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 { }
csharp
// 定义包含所有类型的JsonSerializerContext
[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);
});
csharp
// 使用上下文进行序列化
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);

// 使用上下文进行反序列化
var order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);

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

  • 运行时无反射 - 所有类型信息在编译时生成
  • 兼容AOT - 支持Native AOT发布
  • 速度更快 - 无需运行时类型分析
  • 裁剪安全 - 链接器明确知道所需内容

Protocol Buffers (Protobuf)

Protocol Buffers (Protobuf)

Best for: Actor systems, gRPC, event sourcing, any long-lived wire format.
最适合:Actor系统、gRPC、事件溯源、任何长期使用的有线格式。

Setup

配置

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

Define Schema

定义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;
}
protobuf
// orders.proto
syntax = "proto3";

message Order {
    string id = 1;
    string customer_id = 2;
    repeated OrderItem items = 3;
    int64 created_at_ticks = 4;

    // 添加新字段始终安全
    string notes = 5;  // v2版本新增 - 旧版读取器会忽略它
}

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!
}

protobuf
// 安全操作:使用新编号添加新字段
message Order {
    string id = 1;
    string customer_id = 2;
    string shipping_address = 5;  // 新增 - 安全
}

// 安全操作:移除字段(旧版读取器忽略未知字段,新版读取器使用默认值)
// 只需停止使用该字段,保留字段编号为保留状态
message Order {
    string id = 1;
    // customer_id已移除,但字段2被保留
    reserved 2;
}

// 不安全操作:修改字段类型
message Order {
    int32 id = 1;  // 原类型为string - 会导致兼容问题!
}

// 不安全操作:重用字段编号
message Order {
    reserved 2;
    string new_field = 2;  // 重用编号2 - 会导致兼容问题!
}

MessagePack

MessagePack

Best for: High-performance scenarios, compact payloads, actor messaging.
最适合:高性能场景、紧凑负载、Actor消息传递。

Setup

配置

bash
dotnet add package MessagePack
dotnet add package MessagePack.Annotations
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);
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; }

    // 新字段 - 旧版读取器会跳过未知键
    [Key(4)]
    public string? Notes { get; init; }
}

// 序列化
var bytes = MessagePackSerializer.Serialize(order);

// 反序列化
var order = MessagePackSerializer.Deserialize<Order>(bytes);

AOT-Compatible Setup

兼容AOT的配置

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));

csharp
// 使用源代码生成器实现AOT兼容
[MessagePackObject]
public partial class Order { }  // partial关键字启用源代码生成

// 配置解析器
var options = MessagePackSerializerOptions.Standard
    .WithResolver(CompositeResolver.Create(
        GeneratedResolver.Instance,  // 生成的解析器
        StandardResolver.Instance));

Migrating from Newtonsoft.Json

从Newtonsoft.Json迁移

Common Issues

常见问题

NewtonsoftSystem.Text.JsonFix
$type
in JSON
Not supported by defaultUse discriminators or custom converters
JsonProperty
JsonPropertyName
Different attribute
DefaultValueHandling
DefaultIgnoreCondition
Different API
NullValueHandling
DefaultIgnoreCondition
Different API
Private settersRequires
[JsonInclude]
Explicit opt-in
Polymorphism
[JsonDerivedType]
(.NET 7+)
Explicit discriminators
NewtonsoftSystem.Text.Json修复方案
$type
在JSON中
默认不支持使用鉴别器或自定义转换器
JsonProperty
JsonPropertyName
不同的特性
DefaultValueHandling
DefaultIgnoreCondition
不同的API
NullValueHandling
DefaultIgnoreCondition
不同的API
私有setter需要
[JsonInclude]
显式选择启用
多态性
[JsonDerivedType]
(.NET 7+)
显式鉴别器

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 { }
csharp
// Newtonsoft(基于反射)
public class Order
{
    [JsonProperty("order_id")]
    public string Id { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string? Notes { get; set; }
}

// System.Text.Json(兼容源代码生成)
public sealed record Order(
    [property: JsonPropertyName("order_id")]
    string Id,

    string? Notes  // 通过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" }

csharp
// .NET 7+ 多态性
[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);

// 序列化结果为:
// { "$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
};
旧代码必须能安全忽略未知字段:
csharp
// Protobuf/MessagePack:自动处理 - 跳过未知字段
// System.Text.Json:配置为允许
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");
在部署新格式的写入器之前,先部署反序列化器:
csharp
// 阶段1:添加反序列化器(部署到所有环境)
public Order Deserialize(byte[] data, string manifest) => manifest switch
{
    "Order.V1" => DeserializeV1(data),
    "Order.V2" => DeserializeV2(data),  // 新增 - 可读取V2格式
    _ => throw new NotSupportedException()
};

// 阶段2:启用序列化器(下一版本,在所有环境都部署了V1之后)
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"
}

csharp
// 错误示例:负载中包含类型名称 - 重命名类会破坏有线格式
{
    "$type": "MyApp.Order, MyApp.Core",
    "id": "123"
}

// 正确示例:显式鉴别器 - 重构安全
{
    "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.

近似吞吐量(数值越高越好):
格式序列化反序列化大小
MessagePack★★★★★★★★★★★★★★★
Protobuf★★★★★★★★★★★★★★★
System.Text.Json (源代码生成)★★★★☆★★★★☆★★★☆☆
System.Text.Json (反射)★★★☆☆★★★☆☆★★★☆☆
Newtonsoft.Json★★☆☆☆★★☆☆☆★★★☆☆
对于热点路径,优先选择MessagePack或Protobuf。

Akka.NET Serialization

Akka.NET 序列化

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

对于Akka.NET Actor系统,使用基于Schema的序列化:
hocon
akka {
  actor {
    serializers {
      messagepack = "Akka.Serialization.MessagePackSerializer, Akka.Serialization.MessagePack"
    }
    serialization-bindings {
      "MyApp.Messages.IMessage, MyApp" = messagepack
    }
  }
}

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);
csharp
// 为System.Text.Json使用源代码生成器
[JsonSerializable(typeof(Order))]
public partial class AppJsonContext : JsonSerializerContext { }

// 使用显式字段编号/键
[MessagePackObject]
public class Order
{
    [Key(0)] public string Id { get; init; }
}

// 使用记录类型作为不可变消息类型
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

csharp
// 绝对不要使用BinaryFormatter
var formatter = new BinaryFormatter();  // 安全风险!

// 不要在有线格式中嵌入类型名称
settings.TypeNameHandling = TypeNameHandling.All;  // 重命名时会失效!

// 不要在热点路径中使用基于反射的序列化
JsonConvert.SerializeObject(order);  // 缓慢、不兼容AOT

Resources

资源