mcpField
S
serialization

serialization

github.com/aaronontheweb开发1.2k579 次安装今天更新
代码仓库

关于 serialization

面向 .NET 的序列化实践技能,帮助为 API、消息传递和持久化选择格式,并实现兼容且高效的序列化。

主要功能

  1. 01比较基于架构与反射的格式
  2. 02使用 System.Text.Json 源生成
  3. 03采用 Protobuf 和 MessagePack
  4. 04规划线格式版本演进
  5. 05从 Newtonsoft.Json 迁移

适用场景

  1. 01为 REST 或 gRPC 选择格式
  2. 02为 Native AOT 准备序列化方案
  3. 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 APISystem.Text.Json(源代码生成)标准、兼容 AOT
gRPCProtocol 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 迁移

常见问题
NewtonsoftSystem.Text.Json修复方法
JSON 中的 $type默认不支持使用判别符或自定义转换器
JsonPropertyJsonPropertyName属性不同
DefaultValueHandlingDefaultIgnoreConditionAPI 不同
NullValueHandlingDefaultIgnoreConditionAPI 不同
私有 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

资源

评论

支持 Markdown