mcpField
S
serialization

serialization

github.com/wshaddix開発86527 回インストール35 週間前に更新
リポジトリ

serialization について

.NET 開発者が API、メッセージング、永続化向けの形式を選ぶためのシリアライゼーションガイドです。System.Text.Json、Protobuf、MessagePack などを比較し、AOT 対応、移行、ワイヤ形式の進化も扱います。

主な機能

  1. 01JSON、Protobuf、MessagePack を比較
  2. 02System.Text.Json のソース生成を解説
  3. 03Protobuf と MessagePack の使い方を提示
  4. 04ワイヤ互換性とバージョン管理を解説
  5. 05Newtonsoft.Json からの移行を案内
  6. 06リフレクションベースの注意点を提示

活用シーン

  1. 01API やメッセージ向けの形式選定
  2. 02JSON シリアライゼーションの AOT 対応
  3. 03プロセス間形式の互換性を保った変更
  4. 04Newtonsoft.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 のシリアル化には、AOT 互換性とパフォーマンスのために、ソースジェネレーターを使った System.Text.Json を使用してください。

基本設定
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)

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

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

パッケージ
<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(ソース生成)★★★★☆★★★★☆★★★☆☆
System.Text.Json(リフレクション)★★★☆☆★★★☆☆★★★☆☆
Newtonsoft.Json★★☆☆☆★★☆☆☆★★★☆☆
最適化のヒント
  • JsonSerializerOptions を再利用する -- オプションの作成にはコストがかかります
  • JsonSerializerContext を使う -- ウォームアップコストをなくせます
  • ストリーミングのシナリオでは Utf8JsonWriter / Utf8JsonReader を使う
  • base64 エンコードされた文字列の代わりに、バイナリデータには Protobuf の ByteString を使う
  • 大きなペイロードでは 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. シリアル化するすべての型に対して、[JsonSerializable] を使った JsonSerializerContext を作成します
  5. JObject / JToken による動的なアクセスを、JsonDocument / JsonElement または厳密に型指定されたモデルに置き換えます
  6. シリアル化の往復処理をテストします -- 属性の意味は異なります

Akka.NET のシリアル化

Akka.NET アクターシステムでは、スキーマベースのシリアル化を使用してください:

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 を登録する
  • シリアル化するすべての型に注釈を付ける -- ソースジェネレーターは一覧に記載された型のコードのみを生成します

エージェントが注意すべき点

  1. AOT プロジェクトでは、コンテキストなしで JsonSerializer.Serialize(obj) を使わないでください -- リフレクションにフォールバックします。
  2. コレクション型を [JsonSerializable] に記載し忘れないでください -- [JsonSerializable(typeof(Order))] は List<Order> を対象にしません。
  3. System.Text.Json で Newtonsoft.Json の [JsonProperty] 属性を使わないでください -- 属性は無視されます。
  4. 同じ型階層内で MessagePack の [Key] の整数キーと文字列キーを混在させないでください。
  5. <Protobuf> 項目の GrpcServices 属性を省略しないでください -- 省略すると、クライアントとサーバーの両方のスタブが生成されます。

リソース

コメント