name: dotnet-serialization description: “序列化数据。System.Text.Json 源生成器、Protobuf、MessagePack、AOT 安全模式。” user-invocable: false
dotnet-serialization
为 .NET 应用程序提供的 AOT 友好序列化模式。覆盖 System.Text.Json 源生成器用于编译时序列化、Protocol Buffers (Protobuf) 用于高效二进制序列化,以及 MessagePack 用于高性能紧凑二进制格式。包括性能权衡指导以选择合适的序列化器,以及关于在 AOT 场景中基于反射的序列化的警告。
范围
- System.Text.Json 源生成器用于编译时序列化
- Protocol Buffers (Protobuf) 用于二进制序列化
- MessagePack 用于高性能紧凑格式
- 序列化器选择的性能权衡指导
- AOT 安全序列化模式和反模式
超出范围
- 源生成器编写模式 – 见 [技能:dotnet-csharp-source-generators]
- HTTP 客户端工厂和弹性管道 – 见 [技能:dotnet-http-client] 和 [技能:dotnet-resilience]
- Native AOT 架构和裁剪 – 见 [技能:dotnet-native-aot] 和 [技能:dotnet-trimming]
交叉引用: [技能:dotnet-csharp-source-generators] 用于理解 STJ 源生成器的工作原理。见 [技能:dotnet-integration-testing] 用于测试序列化往返正确性。
序列化格式比较
| 格式 | 库 | 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模式时。适用于缓存层、实时消息传递和高吞吐量场景,其中模式演进通过属性管理。
System.Text.Json 源生成器
System.Text.Json 源生成器生成编译时序列化代码,消除运行时反射。这是 Native AOT 必需 并强烈推荐用于所有新项目。见 [技能:dotnet-csharp-source-generators] 了解底层增量生成器机制。
基本设置
使用 [JsonSerializable] 属性定义 JsonSerializerContext,用于要序列化的每个类型:
using System.Text.Json.Serialization;
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
[JsonSerializable(typeof(OrderStatus))]
public partial class AppJsonContext : JsonSerializerContext
{
}
使用生成上下文
// 序列化
string json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
// 反序列化
Order? result = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);
// 带选项 (创建一次,重用)
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
TypeInfoResolver = AppJsonContext.Default
};
string json = JsonSerializer.Serialize(order, options);
ASP.NET Core 集成
注册源生成上下文,使 Minimal API 自动使用。注意 ConfigureHttpJsonOptions 仅适用于 Minimal API – MVC 控制器需要通过 AddJsonOptions 单独配置:
var builder = WebApplication.CreateBuilder(args);
// Minimal APIs: ConfigureHttpJsonOptions
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
// MVC Controllers: AddJsonOptions (如果使用控制器)
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
var app = builder.Build();
// Minimal API 端点自动使用注册上下文
app.MapGet("/orders/{id}", async (int id, OrderService service) =>
{
var order = await service.GetAsync(id);
return order is not null ? Results.Ok(order) : Results.NotFound();
});
app.MapPost("/orders", async (Order order, OrderService service) =>
{
await service.CreateAsync(order);
return Results.Created($"/orders/{order.Id}", order);
});
组合多个上下文
当应用程序有多个序列化上下文时 (例如,不同的有界上下文或库):
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
{
}
Protobuf 序列化
Protocol Buffers 提供模式优先二进制序列化。Protobuf 是 gRPC 的默认线格式,且 AOT 安全。
包
<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)
当需要缓存、消息传递或文件存储的紧凑负载时,使用 Protobuf 进行二进制序列化而无需 gRPC:
using Google.Protobuf;
// 序列化到字节
byte[] bytes = order.ToByteArray();
// 反序列化从字节
var restored = OrderMessage.Parser.ParseFrom(bytes);
// 序列化到流
using var stream = File.OpenWrite("order.bin");
order.WriteTo(stream);
Proto 文件注册在 .csproj 中
<ItemGroup>
<Protobuf Include="Protos\*.proto" GrpcServices="Both" />
</ItemGroup>
MessagePack 序列化
MessagePack-CSharp 提供高性能二进制序列化,负载比 JSON 小,并与 .NET 良好集成。
包
<PackageReference Include="MessagePack" Version="3.*" />
<!-- 用于 AOT 支持 -->
<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; }
}
序列化
// 序列化
byte[] bytes = MessagePackSerializer.Serialize(order);
// 反序列化
var restored = MessagePackSerializer.Deserialize<Order>(bytes);
// 带压缩 (LZ4)
var lz4Options = MessagePackSerializerOptions.Standard.WithCompression(
MessagePackCompression.Lz4BlockArray);
byte[] compressed = MessagePackSerializer.Serialize(order, lz4Options);
AOT 解析器设置
对于 Native AOT 兼容性,使用 MessagePack 源生成器生成解析器:
// 在项目中,源生成器自动从标注 [MessagePackObject] 的类型生成解析器。
// 在启动时注册生成解析器:
MessagePackSerializer.DefaultOptions = MessagePackSerializerOptions.Standard
.WithResolver(GeneratedResolver.Instance);
反模式: 基于反射的序列化
在 Native AOT 或裁剪场景中,请勿使用基于反射的序列化器。 当链接器移除类型元数据时,基于反射的序列化会在运行时失败。
Newtonsoft.Json (JsonConvert)
Newtonsoft.Json (JsonConvert.SerializeObject / JsonConvert.DeserializeObject) 严重依赖运行时反射。它 不兼容 Native AOT 和裁剪:
// 错误: 基于反射 -- 在 AOT/裁剪下失败
var json = JsonConvert.SerializeObject(order);
var order = JsonConvert.DeserializeObject<Order>(json);
// 正确: 源生成 -- AOT 安全
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
var order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);
System.Text.Json 无源生成器
即使 System.Text.Json 在没有源生成上下文时也会回退到反射:
// 错误: 无上下文 -- 使用运行时反射
var json = JsonSerializer.Serialize(order);
// 正确: 显式上下文 -- 使用源生成代码
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
从 Newtonsoft.Json 迁移路径
- 用
JsonSerializer.Serialize/Deserialize替换JsonConvert.SerializeObject/DeserializeObject - 用
[JsonPropertyName]替换[JsonProperty] - 用 System.Text.Json 的
JsonConverter<T>替换JsonConverter基类 - 为所有序列化类型创建带有
[JsonSerializable]的JsonSerializerContext - 用
JsonDocument/JsonElement或强类型模型替换JObject/JToken动态访问 - 测试序列化往返 – 属性语义在库间不同
性能指导
吞吐量基准 (近似)
| 格式 | 序列化 (操作/秒) | 反序列化 (操作/秒) | 负载大小 |
|---|---|---|---|
| Protobuf | 最高 | 最高 | 最小 |
| MessagePack | 高 | 高 | 小 |
| STJ 源生成器 | 良好 | 良好 | 较大 (文本) |
| STJ 反射 | 中等 | 中等 | 较大 (文本) |
| Newtonsoft.Json | 较低 | 较低 | 较大 (文本) |
优化技巧
- 重用
JsonSerializerOptions– 创建选项成本高;创建一次并重用 - 使用
JsonSerializerContext– 消除预热成本并减少分配 - 使用
Utf8JsonWriter/Utf8JsonReader用于流式场景,其中无需完全物化处理 JSON - **在 JSON 中使用 Protobuf
ByteString处理二进制数据,而不是 base64 编码字符串 - 启用 MessagePack LZ4 压缩 用于大负载传输
关键原则
- 默认为 System.Text.Json 带源生成器 用于所有 JSON 序列化 – 它是 AOT 安全、快速且内置框架的
- 使用 Protobuf 用于服务间二进制序列化 – 尤其是作为 gRPC 的线格式
- 使用 MessagePack 用于高吞吐量缓存和实时 – 当二进制紧凑性重要但不想管理
.proto模式时 - 在新 AOT 目标项目中切勿使用 Newtonsoft.Json – 它是基于反射的,不兼容裁剪
- 在 ASP.NET Core 中始终注册
JsonSerializerContext– 使用ConfigureHttpJsonOptions用于 Minimal API 和AddJsonOptions用于 MVC 控制器 (它们是分开的注册) - 标注所有序列化类型 – STJ 源生成器仅生成在
[JsonSerializable]中列出的类型的代码;MessagePack 需要[MessagePackObject]
见 [技能:dotnet-native-aot] 了解综合 AOT 编译管道,[技能:dotnet-aot-architecture] 了解 AOT 优先设计模式,以及 [技能:dotnet-trimming] 了解裁剪策略和 ILLink 描述符配置。
代理注意事项
- 在 AOT 项目中不要使用
JsonSerializer.Serialize(obj)而无上下文 – 它会回退到反射并在运行时失败。始终传递源生成的TypeInfo。 - 不要忘记在
[JsonSerializable]中列出集合类型 –[JsonSerializable(typeof(Order))]不覆盖List<Order>。单独添加[JsonSerializable(typeof(List<Order>))]。 - 不要将 Newtonsoft.Json
[JsonProperty]属性与 System.Text.Json 一起使用 – 它们会被静默忽略。改用[JsonPropertyName]。 - 不要在同一类型层次结构中混合 MessagePack
[Key]整数键和字符串键 – 选择一种策略并保持一致。 - 不要省略
<Protobuf>项目上的GrpcServices属性 – 没有它,会生成客户端和服务器存根,如果只需要一个可能导致构建错误。