mcpField
S
serialization

serialization

github.com/aaronontheweb開発1.2k579 回インストール今日更新
リポジトリ

serialization について

API、メッセージング、永続化に適した形式を選び、互換性と効率を考慮したシリアライズを実装する .NET スキルです。

主な機能

  1. 01スキーマ方式とリフレクション方式の比較
  2. 02System.Text.Json のソース生成
  3. 03Protobuf と MessagePack の利用
  4. 04ワイヤ形式のバージョニング設計
  5. 05Newtonsoft.Json からの移行

活用シーン

  1. 01REST または gRPC の形式選定
  2. 02Native 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ネイティブ形式で、バージョン管理に優れている
アクターメッセージングMessagePack または Protobufコンパクトで高速、バージョン互換性が高い
イベントソーシングProtobuf または MessagePack古いイベントを将来にわたって処理できる必要がある
キャッシュMessagePackコンパクトで高速
構成JSON(System.Text.Json)人間が読みやすい
ログ記録JSON(System.Text.Json)構造化されており、解析可能
避けるべき形式
形式問題
BinaryFormatterセキュリティ上の脆弱性があり、非推奨です。絶対に使用しないでください
Newtonsoft.Json の既定設定ペイロード内の型名は、名前変更時に問題を引き起こします
DataContractSerializer複雑で、バージョン管理に適していません
XML冗長で低速、複雑

ソースジェネレーターを使用した System.Text.Json

JSON のシリアル化には、AOT 互換性とパフォーマンスのために、ソースジェネレーターを使用した System.Text.Json を使います。

セットアップ
// 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)

最適な用途:アクターシステム、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

最適な用途:高パフォーマンスが求められるシナリオ、コンパクトなペイロード、アクターメッセージング。

セットアップ
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 が異なります
プライベートセッター[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 のアクターシステムでは、スキーマベースのシリアライズを使用してください:

akka {
  actor {
    serializers {
      messagepack = "Akka.Serialization.MessagePackSerializer, Akka.Serialization.MessagePack"
    }
    serialization-bindings {
      "MyApp.Messages.IMessage, MyApp" = messagepack
    }
  }
}

詳しくは、Akka.NET Serialization Docs を参照してください。


ベストプラクティス

推奨事項
// 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

リソース

コメント