mcpField
S
serialization

serialization

github.com/wshaddix开发86527 次安装35 周前更新
代码仓库

关于 serialization

面向 .NET 开发者的序列化指南,帮助为 API、消息和持久化场景选择格式,并处理 AOT 兼容性、迁移与线格式演进。内容比较 System.Text.Json、Protobuf 和 MessagePack 等方案。

主要功能

  1. 01比较 JSON、Protobuf 与 MessagePack
  2. 02介绍 System.Text.Json 源生成
  3. 03说明 Protobuf 与 MessagePack 用法
  4. 04涵盖线格式兼容和版本演进
  5. 05提供 Newtonsoft.Json 迁移建议
  6. 06列出反射序列化的注意事项

适用场景

  1. 01为 API 或消息选择序列化格式
  2. 02让 JSON 序列化适用于 AOT
  3. 03规划跨进程数据格式的兼容演进
  4. 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人类可读相对大小相对速度最适合
JSONSystem.Text.Json(源生成)是是最大良好API、配置、Web 客户端
ProtobufGoogle.Protobuf是否最小最快服务间通信、gRPC 线路格式
MessagePackMessagePack-CSharp是(使用 AOT 解析器)否小快高吞吐量缓存、实时场景
JSONNewtonsoft.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 迁移
  1. 将 JsonConvert.SerializeObject / DeserializeObject 替换为 JsonSerializer.Serialize / Deserialize
  2. 将 [JsonProperty] 替换为 [JsonPropertyName]
  3. 将 JsonConverter 基类替换为 System.Text.Json 中的 JsonConverter<T>
  4. 创建一个 JsonSerializerContext,并使用 [JsonSerializable] 标注所有序列化类型
  5. 将 JObject / JToken 动态访问替换为 JsonDocument / JsonElement 或强类型模型
  6. 测试序列化往返结果——属性语义有所不同

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 注意事项

  1. 在 AOT 项目中,不要在未提供上下文的情况下使用 JsonSerializer.Serialize(obj)——这会回退到反射。
  2. 不要忘记在 [JsonSerializable] 中列出集合类型——[JsonSerializable(typeof(Order))] 不包含 List<Order>。
  3. 不要在 System.Text.Json 中使用 Newtonsoft.Json 的 [JsonProperty] 属性——这些属性会被静默忽略。
  4. 不要在同一类型层次结构中混用 MessagePack 的 [Key] 整数键和 [Key] 字符串键。
  5. 不要省略 <Protobuf> 项上的 GrpcServices 属性——否则会同时生成客户端和服务端存根。

资源

评论

支持 Markdown