1. はじめに
デフォルトの挙動で問題ないならそれでいいです。でも、クライアント(あるいはサードパーティのバックエンド)が厳密なフォーマットを要求することがよくあります:どこでも camelCase、日付は ISO 8601、null を省略、カスタムコンバーターなど。インテグレーションの喜びが待ってます。
そんなときに登場するのが今回の友達 — クラス JsonSerializerOptions です。
シリアライズをキッチンに例えるなら、このクラスはスパイスや鍋、秘密の材料で、シリアライズを自分好みに味付けできます。
JsonSerializerOptions の使い方
シリアライズ/デシリアライズの挙動をカスタマイズするには、設定オブジェクトをシリアライザーのメソッドに渡すだけです:
using System.Text.Json;
var options = new JsonSerializerOptions
{
// ここに設定を書く
};
string json = JsonSerializer.Serialize(myObject, options);
var obj = JsonSerializer.Deserialize<MyType>(json, options);
このパターンはどこでも見かけます:JsonSerializerOptions を作って好みに合わせて設定し(下記参照)、シリアライザーの Serialize/Deserialize に渡します。
重要: 同じ JsonSerializerOptions オブジェクトは複数のシリアライズで再利用できます(むしろ推奨)。
2. JsonSerializerOptions の基本設定
このクラスのよく使われる「つまみ」と「スイッチ」を見ていきましょう。
JSON の整形 — WriteIndented
一行の JSON が目にチクチクする? 大丈夫、インデント付きの整形をオンにしましょう。
var options = new JsonSerializerOptions
{
WriteIndented = true // 見やすい「人間向け」JSON
};
var person = new Person { Name = "Anna", Age = 30 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json);
出力:
{
"Name": "Anna",
"Age": 30
}
WriteIndented = true を指定しなければ退屈な {"Name":"Anna","Age":30} になります。プロダクションではサイズが小さいのでミニファイされた JSON を使い、デバッグやログでは整形されたものを使うことが多いです。
ケース命名 — PropertyNamingPolicy
API がプロパティ名を camelCase (例:firstName)で要求することがあります。.NET では通常プロパティは PascalCase なので、PropertyNamingPolicy が役に立ちます:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var person = new Person { Name = "Oleg", Age = 25 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json); // {"name":"Oleg","age":25}
もし全て大文字みたいな独自スタイルが必要なら、自分で JsonNamingPolicy を実装すればOK。通常は CamelCase で十分です。
3. プロパティの無視
null 値をスキップするかどうか — DefaultIgnoreCondition
<null のフィールドを JSON に出したくないことがあります。理由は JSON を小さくするためや、外部システムが null を扱えないためなどです。
これにはプロパティ DefaultIgnoreCondition を使います:
using System.Text.Json.Serialization;
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
var person = new Person { Name = null, Age = 22 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json); // {"Age":22}
もし null だけでなくデフォルト値(例えば int=0)も無視したければ、WhenWritingDefault を使います:
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault;
プライベートなプロパティを扱う — IncludeFields と [JsonInclude]
デフォルトではパブリックなプロパティ(公開セッターを持つもの)だけがシリアライズされます。フィールド(fields)もシリアライズしたければ次を有効にします:
var options = new JsonSerializerOptions
{
IncludeFields = true
};
クラスの例:
public class Item
{
public string Name;
public int Id { get; set; }
}
var item = new Item { Name = "Book", Id = 12 };
string json = JsonSerializer.Serialize(item, options);
// {"Name":"Book","Id":12}
プライベートなフィールド/プロパティをシリアライズしたければ属性 [JsonInclude] を使う方法もありますが、それは別のレクチャーのテーマです。
4. 値の変換
日付のフォーマット — Converters
デフォルトでは System.Text.Json は日付を厳密に ISO 8601 形式(例:"2023-12-27T15:30:45.123Z")でシリアライズします。ほとんどのケースで便利ですが、特別な日付形式が必要になることもあります。
その場合はカスタムの コンバーター を追加します。基本的な例:
using System.Text.Json;
using System.Text.Json.Serialization;
public class CustomDateTimeConverter : JsonConverter<DateTime>
{
private string _format = "yyyyMMdd";
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var date = reader.GetString();
return DateTime.ParseExact(date, _format, null);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_format));
}
}
// コンバーターを使う
var options = new JsonSerializerOptions();
options.Converters.Add(new CustomDateTimeConverter());
var dateObj = new { Date = new DateTime(2022, 1, 5) };
string json = JsonSerializer.Serialize(dateObj, options); // {"Date":"20220105"}
5. 便利な注意点
デシリアライズ時にプロパティ名の大文字小文字を無視する — PropertyNameCaseInsensitive
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var json = "{\"name\":\"Vasya\",\"age\":33}";
var person = JsonSerializer.Deserialize<Person>(json, options);
// person.Name == "Vasya"
ネストと最大オブジェクト深度の制御 — MaxDepth
var options = new JsonSerializerOptions
{
MaxDepth = 32 // デフォルトは 64
};
ただし、オブジェクトが無限にネストされうる(例えば親子参照を持つツリー)場合は、そもそもモデルを別設計にした方がいいです。
JSON のコメント — ReadCommentHandling(デシリアライズ)
公式には JSON にコメントはありませんが、時々 // コメント付きの "独創的" なファイルに遭遇します。
var options = new JsonSerializerOptions
{
ReadCommentHandling = JsonCommentHandling.Skip
};
string json = "{\n \"Name\": \"Ivan\", // ユーザー名\n \"Age\": 30\n}";
var person = JsonSerializer.Deserialize<Person>(json, options);
特殊文字のサポート — Encoder
どのようにシリアライザーが文字をエスケープするか(例えばキリル文字を \uXXXX に変換しないように)を制御したいことがあります。独自のエンコーダを指定できます:
using System.Text.Encodings.Web;
var options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
使うときは注意してください。さもないと絵文字や漢字が突然 Unicode シーケンスに変換されるかもしれません!
6. シリアライズ設定でよくあるミス
ミス #1: 公開セッターを忘れている。 セッターが公開されていない、あるいはプロパティがプライベートなのでシリアライズされない、という落とし穴。
ミス #2: null 値の誤った無視設定。 DefaultIgnoreCondition をオンにすると null のプロパティが JSON に現れないので期待外れになることがあります。
ミス #3: 日付フォーマットの不一致。 日付が期待通りにシリアライズされない場合はカスタムコンバーター(JsonConverter)を追加しましょう。
ミス #4: プロパティの順序を期待している。 JSON 標準はフィールドの順序を保証しません。API が厳密な順序を要求するなら、モデルを変えるか別ライブラリを使う必要があります。
ミス #5: シリアライズとデシリアライズで設定が違う。 書き込みと読み込みで同じ JsonSerializerOptions を使わないと、名前の大文字小文字やフォーマットで問題が起きます。
GO TO FULL VERSION