CodeGym /コース /C# SELF /シリアライズの設定 ( JsonSerializerO...

シリアライズの設定 ( JsonSerializerOptions)

C# SELF
レベル 45 , レッスン 3
使用可能

1. はじめに

デフォルトの挙動で問題ないならそれでいいです。でも、クライアント(あるいはサードパーティのバックエンド)が厳密なフォーマットを要求することがよくあります:どこでも camelCase、日付は ISO 8601null を省略、カスタムコンバーターなど。インテグレーションの喜びが待ってます。

そんなときに登場するのが今回の友達 — クラス 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 を使わないと、名前の大文字小文字やフォーマットで問題が起きます。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION