关于 serialization
面向 .NET 的序列化实践技能,帮助为 API、消息传递和持久化选择格式,并实现兼容且高效的序列化。
主要功能
- 01比较基于架构与反射的格式
- 02使用 System.Text.Json 源生成
- 03采用 Protobuf 和 MessagePack
- 04规划线格式版本演进
- 05从 Newtonsoft.Json 迁移
适用场景
- 01为 REST 或 gRPC 选择格式
- 02为 Native AOT 准备序列化方案
- 03维护分布式服务间的兼容性
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ aaronontheweb/ dotnet-skills --skill serialization安装后在 Claude Code 中输入/serialization确认已加载。
详细介绍
.NET 中的序列化
何时使用此技能
在以下情况下使用此技能:
- 为 API、消息传递或持久化选择序列化格式
- 从 Newtonsoft.Json 迁移到 System.Text.Json
- 实现兼容 AOT 的序列化
- 为分布式系统设计线格式
- 优化序列化性能
基于架构与基于反射
| 方面 | 基于架构 | 基于反射 |
|---|---|---|
| 示例 | Protobuf、MessagePack、System.Text.Json(源代码生成) | Newtonsoft.Json、BinaryFormatter |
| 负载中的类型信息 | 否(外部架构) | 是(嵌入类型名称) |
| 版本管理 | 显式字段编号/名称 | 隐式(类型结构) |
| 性能 | 快(无需反射) | 较慢(运行时反射) |
| 兼容 AOT | 是 | 否 |
| 线兼容性 | 极佳 | 较差 |
建议:对于任何跨进程边界传输的数据,都使用基于架构的序列化。
格式建议
| 使用场景 | 推荐格式 | 原因 |
|---|---|---|
| REST API | System.Text.Json(源代码生成) | 标准、兼容 AOT |
| gRPC | Protocol Buffers | 原生格式,版本管理能力极佳 |
| Actor 消息传递 | MessagePack 或 Protobuf | 紧凑、快速、版本安全 |
| 事件溯源 | Protobuf 或 MessagePack | 必须永久处理旧事件 |
| 缓存 | MessagePack | 紧凑、快速 |
| 配置 | JSON(System.Text.Json) | 人类可读 |
| 日志记录 | JSON(System.Text.Json) | 结构化、可解析 |
应避免的格式
| 格式 | 问题 |
|---|---|
| BinaryFormatter | 存在安全漏洞、已弃用,切勿使用 |
| Newtonsoft.Json 默认配置 | 负载中的类型名称会因重命名而失效 |
| DataContractSerializer | 复杂,版本管理能力差 |
| XML | 冗长、缓慢、复杂 |
使用源代码生成器的 System.Text.Json
进行 JSON 序列化时,使用带源代码生成器的 System.Text.Json,以兼容 AOT 并提升性能。
设置
// 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 { }
用法
// 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);
});
优点
- 运行时无需反射 - 所有类型信息均在编译时生成
- 兼容 AOT - 可用于 Native AOT 发布
- 更快 - 无需在运行时分析类型
- 适用于裁剪 - 链接器能准确判断所需内容
Protocol Buffers(Protobuf)
最适合:Actor 系统、gRPC、事件溯源,以及任何需要长期使用的线格式。
设置
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools
定义架构
// 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;
}
版本管理规则
// 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
最适合:高性能场景、紧凑负载、Actor 消息传递。
设置
dotnet add package MessagePack
dotnet add package MessagePack.Annotations
使用契约
[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 的设置
// 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));
从 Newtonsoft.Json 迁移
常见问题
| Newtonsoft | System.Text.Json | 修复方法 |
|---|---|---|
JSON 中的 $type | 默认不支持 | 使用判别符或自定义转换器 |
JsonProperty | JsonPropertyName | 属性不同 |
DefaultValueHandling | DefaultIgnoreCondition | API 不同 |
NullValueHandling | DefaultIgnoreCondition | API 不同 |
| 私有 setter | 需要 [JsonInclude] | 显式启用 |
| 多态 | [JsonDerivedType](.NET 7+) | 显式指定判别符 |
迁移模式
// 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 { }
使用判别符实现多态
// .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" }
线兼容性模式
宽容读取器
旧代码必须能够安全地忽略未知字段:
// Protobuf/MessagePack: Automatic - unknown fields skipped
// System.Text.Json: Configure to allow
var options = new JsonSerializerOptions
{
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip
};
先部署读取,再部署写入
针对新格式,先部署反序列化器,再部署序列化器:
// 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");
切勿嵌入类型名称
// 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"
}
性能对比
大致吞吐量(越高越好):
| 格式 | 序列化 | 反序列化 | 大小 |
|---|---|---|---|
| MessagePack | ★★★★★ | ★★★★★ | ★★★★★ |
| Protobuf | ★★★★★ | ★★★★★ | ★★★★★ |
| System.Text.Json(源代码生成) | ★★★★☆ | ★★★★☆ | ★★★☆☆ |
| System.Text.Json(反射) | ★★★☆☆ | ★★★☆☆ | ★★★☆☆ |
| Newtonsoft.Json | ★★☆☆☆ | ★★☆☆☆ | ★★★☆☆ |
对于高频路径,优先使用 MessagePack 或 Protobuf。
Akka.NET 序列化
对于 Akka.NET Actor 系统,请使用基于架构的序列化:
akka {
actor {
serializers {
messagepack = "Akka.Serialization.MessagePackSerializer, Akka.Serialization.MessagePack"
}
serialization-bindings {
"MyApp.Messages.IMessage, MyApp" = messagepack
}
}
}
请参阅 Akka.NET 序列化文档。
最佳实践
应该做
// 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 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
资源
- System.Text.Json 源代码生成: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 序列化:https://getakka.net/articles/networking/serialization.html
- 线缆兼容性:https://getakka.net/community/contributing/wire-compatibility.html
评论
支持 Markdown