关于 serialization
面向 .NET 开发者的序列化指南,帮助为 API、消息和持久化场景选择格式,并处理 AOT 兼容性、迁移与线格式演进。内容比较 System.Text.Json、Protobuf 和 MessagePack 等方案。
主要功能
- 01比较 JSON、Protobuf 与 MessagePack
- 02介绍 System.Text.Json 源生成
- 03说明 Protobuf 与 MessagePack 用法
- 04涵盖线格式兼容和版本演进
- 05提供 Newtonsoft.Json 迁移建议
- 06列出反射序列化的注意事项
适用场景
- 01为 API 或消息选择序列化格式
- 02让 JSON 序列化适用于 AOT
- 03规划跨进程数据格式的兼容演进
- 04从 Newtonsoft.Json 迁移到 System.Text.Json
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill serialization安装后在 Claude Code 中输入/serialization确认已加载。
详细介绍
.NET 中的序列化
何时使用此技能
在以下情况下使用此技能:
- 为 API、消息传递或持久化选择序列化格式
- 从 Newtonsoft.Json 迁移到 System.Text.Json
- 实现兼容 AOT 的序列化
- 为分布式系统设计线路格式
- 优化序列化性能
序列化格式比较
| 格式 | 库 | 兼容 AOT | 人类可读 | 相对大小 | 相对速度 | 最适合 |
|---|---|---|---|---|---|---|
| JSON | System.Text.Json(源生成) | 是 | 是 | 最大 | 良好 | API、配置、Web 客户端 |
| Protobuf | Google.Protobuf | 是 | 否 | 最小 | 最快 | 服务间通信、gRPC 线路格式 |
| MessagePack | MessagePack-CSharp | 是(使用 AOT 解析器) | 否 | 小 | 快 | 高吞吐量缓存、实时场景 |
| JSON | Newtonsoft.Json | 否(反射) | 是 | 最大 | 较慢 | 仅用于旧版项目——不要用于 AOT |
如何选择
- 使用源生成器的 System.Text.Json:API、配置,以及需要人类可读输出或供 Web 客户端使用的任何场景的默认选择。兼容 AOT。
- Protobuf:gRPC 的默认线路格式。服务间通信的吞吐量最高,负载最小。使用
.proto文件进行模式优先开发。 - MessagePack:需要二进制紧凑性,但不想管理
.proto模式时使用。适用于缓存层、实时消息传递和高吞吐量场景。
基于模式与基于反射
| 方面 | 基于模式 | 基于反射 |
|---|---|---|
| 示例 | Protobuf、MessagePack、System.Text.Json(源生成) | Newtonsoft.Json、BinaryFormatter |
| 负载中的类型信息 | 否(外部模式) | 是(嵌入类型名称) |
| 版本控制 | 显式字段编号/名称 | 隐式(类型结构) |
| 性能 | 快(无需反射) | 较慢(运行时反射) |
| 兼容 AOT | 是 | 否 |
| 线路兼容性 | 优秀 | 较差 |
建议:对任何跨进程边界传输的数据,都使用基于模式的序列化。
应避免的格式
| 格式 | 问题 |
|---|---|
| BinaryFormatter | 存在安全漏洞,已弃用,绝不要使用 |
| Newtonsoft.Json 默认配置 | 负载中的类型名称会在重命名时出错 |
| DataContractSerializer | 复杂,版本控制能力差 |
| XML | 冗长、缓慢、复杂 |
使用源生成器的 System.Text.Json
进行 JSON 序列化时,使用带源生成器的 System.Text.Json,以兼容 AOT 并提升性能。
基本设置
using System.Text.Json.Serialization;
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
[JsonSerializable(typeof(OrderStatus))]
public partial class AppJsonContext : JsonSerializerContext
{
}
使用生成的上下文
// Serialize
string json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
// Deserialize
Order? result = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);
// With options
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
TypeInfoResolver = AppJsonContext.Default
};
string json = JsonSerializer.Serialize(order, options);
ASP.NET Core 集成
var builder = WebApplication.CreateBuilder(args);
// Minimal APIs
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
// MVC Controllers
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
组合多个上下文
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine(
AppJsonContext.Default,
CatalogJsonContext.Default,
InventoryJsonContext.Default
);
});
常见配置
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false)]
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
public partial class AppJsonContext : JsonSerializerContext
{
}
处理多态
[JsonDerivedType(typeof(CreditCardPayment), "credit_card")]
[JsonDerivedType(typeof(BankTransferPayment), "bank_transfer")]
[JsonDerivedType(typeof(WalletPayment), "wallet")]
public abstract class Payment
{
public decimal Amount { get; init; }
public string Currency { get; init; } = "USD";
}
public class CreditCardPayment : Payment
{
public string Last4Digits { get; init; } = "";
}
[JsonSerializable(typeof(Payment))]
public partial class AppJsonContext : JsonSerializerContext
{
}
Protocol Buffers(Protobuf)
最适用于:Actor 系统、gRPC、事件溯源,以及任何长期使用的线路格式。
包
<PackageReference Include="Google.Protobuf" Version="3.*" />
<PackageReference Include="Grpc.Tools" Version="2.*" PrivateAssets="All" />
Proto 文件
syntax = "proto3";
import "google/protobuf/timestamp.proto";
option csharp_namespace = "MyApp.Contracts";
message OrderMessage {
int32 id = 1;
string customer_id = 2;
repeated OrderItemMessage items = 3;
google.protobuf.Timestamp created_at = 4;
}
message OrderItemMessage {
string product_id = 1;
int32 quantity = 2;
double unit_price = 3;
}
独立使用 Protobuf(不使用 gRPC)
using Google.Protobuf;
// Serialize to bytes
byte[] bytes = order.ToByteArray();
// Deserialize from bytes
var restored = OrderMessage.Parser.ParseFrom(bytes);
// Serialize to stream
using var stream = File.OpenWrite("order.bin");
order.WriteTo(stream);
在 .csproj 中注册 Proto 文件
<ItemGroup>
<Protobuf Include="Protos\*.proto" GrpcServices="Both" />
</ItemGroup>
版本控制规则
// 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 (keep the number reserved)
message Order {
string id = 1;
reserved 2; // customer_id removed
}
// 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 消息传递。
包
<PackageReference Include="MessagePack" Version="3.*" />
<PackageReference Include="MessagePack.SourceGenerator" Version="3.*" />
使用源生成器进行基本操作(兼容 AOT)
using MessagePack;
[MessagePackObject]
public partial class Order
{
[Key(0)]
public int Id { get; init; }
[Key(1)]
public string CustomerId { get; init; } = "";
[Key(2)]
public List<OrderItem> Items { get; init; } = [];
[Key(3)]
public DateTimeOffset CreatedAt { get; init; }
[Key(4)]
public string? Notes { get; init; }
}
序列化
// Serialize
byte[] bytes = MessagePackSerializer.Serialize(order);
// Deserialize
var restored = MessagePackSerializer.Deserialize<Order>(bytes);
// With compression (LZ4)
var lz4Options = MessagePackSerializerOptions.Standard.WithCompression(
MessagePackCompression.Lz4BlockArray);
byte[] compressed = MessagePackSerializer.Serialize(order, lz4Options);
AOT 解析器设置
MessagePackSerializer.DefaultOptions = MessagePackSerializerOptions.Standard
.WithResolver(GeneratedResolver.Instance);
线路兼容性模式
宽容读取器
旧代码必须能够安全地忽略未知字段:
// 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 (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",
"id": 123
}
// GOOD: Explicit discriminator - refactoring safe
{
"type": "order",
"id": 123
}
性能对比
近似吞吐量(越高越好):
| 格式 | 序列化 | 反序列化 | 大小 |
|---|---|---|---|
| MessagePack | ★★★★★ | ★★★★★ | ★★★★★ |
| Protobuf | ★★★★★ | ★★★★★ | ★★★★★ |
| System.Text.Json (source gen) | ★★★★☆ | ★★★★☆ | ★★★☆☆ |
| System.Text.Json (reflection) | ★★★☆☆ | ★★★☆☆ | ★★★☆☆ |
| Newtonsoft.Json | ★★☆☆☆ | ★★☆☆☆ | ★★★☆☆ |
优化技巧
- 复用
JsonSerializerOptions-- 创建选项的开销很大 - 使用
JsonSerializerContext-- 可消除预热开销 - 在流式处理场景中使用
Utf8JsonWriter/Utf8JsonReader - 处理二进制数据时使用 Protobuf
ByteString,而非 Base64 编码字符串 - 对大型负载启用 MessagePack LZ4 压缩
反模式:基于反射的序列化
在 Native AOT 或裁剪场景中,请勿使用基于反射的序列化器。
Newtonsoft.Json (JsonConvert)
// BAD: Reflection-based -- fails under AOT/trimming
var json = JsonConvert.SerializeObject(order);
var order = JsonConvert.DeserializeObject<Order>(json);
// GOOD: Source-generated -- AOT-safe
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
var order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);
不使用源生成器的 System.Text.Json
// BAD: No context -- uses runtime reflection
var json = JsonSerializer.Serialize(order);
// GOOD: Explicit context -- uses source-generated code
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
从 Newtonsoft.Json 迁移
- 将
JsonConvert.SerializeObject/DeserializeObject替换为JsonSerializer.Serialize/Deserialize - 将
[JsonProperty]替换为[JsonPropertyName] - 将
JsonConverter基类替换为 System.Text.Json 中的JsonConverter<T> - 创建一个
JsonSerializerContext,并使用[JsonSerializable]标注所有序列化类型 - 将
JObject/JToken动态访问替换为JsonDocument/JsonElement或强类型模型 - 测试序列化往返结果——属性语义有所不同
Akka.NET 序列化
对于 Akka.NET Actor 系统,请使用基于架构的序列化:
akka {
actor {
serializers {
messagepack = "Akka.Serialization.MessagePackSerializer, Akka.Serialization.MessagePack"
}
serialization-bindings {
"MyApp.Messages.IMessage, MyApp" = messagepack
}
}
}
核心原则
- JSON 序列化默认使用带源生成器的 System.Text.Json
- 服务间二进制序列化使用 Protobuf
- 高吞吐量缓存和实时场景使用 MessagePack
- 新的 AOT 目标项目绝不使用 Newtonsoft.Json
- 始终在 ASP.NET Core 中注册
JsonSerializerContext - 标注所有序列化类型——源生成器只会为已列出的类型生成代码
Agent 注意事项
- 在 AOT 项目中,不要在未提供上下文的情况下使用
JsonSerializer.Serialize(obj)——这会回退到反射。 - 不要忘记在
[JsonSerializable]中列出集合类型——[JsonSerializable(typeof(Order))]不包含List<Order>。 - 不要在 System.Text.Json 中使用 Newtonsoft.Json 的
[JsonProperty]属性——这些属性会被静默忽略。 - 不要在同一类型层次结构中混用 MessagePack 的
[Key]整数键和[Key]字符串键。 - 不要省略
<Protobuf>项上的GrpcServices属性——否则会同时生成客户端和服务端存根。
资源
- System.Text.Json 源生成:https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation
- 从 Newtonsoft.Json 迁移到 System.Text.Json:https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft
- 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
- Native AOT 部署:https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
评论
支持 Markdown