serialization
.NETアプリケーションに適したシリアライゼーション形式を選択してください。リフレクション依存の形式(Newtonsoft.Json)よりも、スキーマベースの形式(Protobuf、MessagePack)を優先してください。JSONのシナリオではAOTソースジェネレータを備えたSystem.Text.Jsonを使用してください。
description の原文を見る
Choose the right serialization format for .NET applications. Prefer schema-based formats (Protobuf, MessagePack) over reflection-based (Newtonsoft.Json). Use System.Text.Json with AOT source generators for JSON scenarios.
SKILL.md 本文
.NETでのシリアライゼーション
このスキルを使用するタイミング
以下の場合にこのスキルを使用してください:
- API、メッセージング、または永続化用のシリアライゼーション形式を選択する
- Newtonsoft.Jsonからシステム.Text.Jsonに移行する
- AOT互換のシリアライゼーションを実装する
- 分散システムのワイヤー形式を設計する
- シリアライゼーションのパフォーマンスを最適化する
スキーマベース vs リフレクションベース
| 側面 | スキーマベース | リフレクションベース |
|---|---|---|
| 例 | Protobuf、MessagePack、System.Text.Json(ソース生成) | Newtonsoft.Json、BinaryFormatter |
| ペイロード内の型情報 | いいえ(外部スキーマ) | はい(型名がに埋め込まれている) |
| バージョニング | 明示的なフィールド番号/名前 | 暗黙的(型構造) |
| パフォーマンス | 高速(リフレクションなし) | 低速(ランタイムリフレクション) |
| AOT互換 | はい | いいえ |
| ワイヤー互換性 | 優秀 | 不良 |
推奨事項: プロセス境界を越えるシリアライゼーションはスキーマベースを使用してください。
形式の推奨事項
| ユースケース | 推奨形式 | 理由 |
|---|---|---|
| REST API | System.Text.Json(ソース生成) | 標準、AOT互換 |
| gRPC | Protocol 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からの移行
よくある問題
| Newtonsoft | System.Text.Json | 修正 |
|---|---|---|
$type in JSON | デフォルトではサポートされていない | ディスクリミネーターまたはカスタムコンバーターを使用 |
JsonProperty | JsonPropertyName | 異なる属性 |
DefaultValueHandling | DefaultIgnoreCondition | 異なるAPI |
NullValueHandling | DefaultIgnoreCondition | 異なるAPI |
| プライベートセッター | [JsonInclude] が必要 | 明示的なオプトイン |
| ポリモーフィズム | [JsonDerivedType] (.NET 7以降) | 明示的なディスクリミネーター |
移行パターン
// Newtonsoft (リフレクションベース)
public class Order
{
[JsonProperty("order_id")]
public string Id { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string? Notes { get; set; }
}
// System.Text.Json (ソース生成互換)
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+ ポリモーフィズム
[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: 自動 - 未知のフィールドはスキップ
// System.Text.Json: スキップを許可するように構成
var options = new JsonSerializerOptions
{
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip
};
読み取りを書き込みより前に導入
新しい形式のデシリアライザーを最初にデプロイします:
// フェーズ1: デシリアライザーを追加(すべての場所にデプロイ)
public Order Deserialize(byte[] data, string manifest) => manifest switch
{
"Order.V1" => DeserializeV1(data),
"Order.V2" => DeserializeV2(data), // NEW - V2を読み取り可能
_ => throw new NotSupportedException()
};
// フェーズ2: シリアライザーを有効化(次のリリース、V1がすべての場所にデプロイ後)
public (byte[] data, string manifest) Serialize(Order order) =>
_useV2Format
? (SerializeV2(order), "Order.V2")
: (SerializeV1(order), "Order.V1");
型名をペイロードに埋め込まない
// 悪い例: ペイロード内の型名 - クラスのリネーム時にワイヤー形式が破損
{
"$type": "MyApp.Order, MyApp.Core",
"id": "123"
}
// 良い例: 明示的なディスクリミネーター - リファクタリング安全
{
"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シリアライゼーションドキュメントを参照してください。
ベストプラクティス
実施すること
// System.Text.Jsonにソース生成を使用
[JsonSerializable(typeof(Order))]
public partial class AppJsonContext : JsonSerializerContext { }
// 明示的なフィールド番号/キーを使用
[MessagePackObject]
public class Order
{
[Key(0)] public string Id { get; init; }
}
// 不変メッセージ型にはレコードを使用
public sealed record OrderCreated(OrderId Id, CustomerId CustomerId);
実施しないこと
// BinaryFormatterを使用しないでください(絶対に)
var formatter = new BinaryFormatter(); // セキュリティリスク!
// ワイヤー形式に型名を埋め込まないでください
settings.TypeNameHandling = TypeNameHandling.All; // リネーム時に破損!
// ホットパスにリフレクションシリアライゼーションを使用しないでください
JsonConvert.SerializeObject(order); // 低速、AOT互換ではない
リソース
- System.Text.Jsonソース生成: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation
- Protocol Buffers: https://protobuf.dev/
- MessagePack-CSharp: https://github.com/MessagePack-CSharp/MessagePack-CSharp
- Akka.NETシリアライゼーション: https://getakka.net/articles/networking/serialization.html
- ワイヤー互換性: https://getakka.net/community/contributing/wire-compatibility.html
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- woutervanranst
- ライセンス
- Apache-2.0
- 最終更新
- 2026/5/12
Source: https://github.com/woutervanranst/Arius7 / ライセンス: Apache-2.0