タグ: JSON

  • 【C#/.NET8】System.Text.JsonでJSON文字列をオブジェクトにデシリアライズする方法

    前回の記事では、.NET 8 における標準ライブラリ System.Text.Json を使って、オブジェクトをJSON文字列にシリアライズする方法をご紹介しました。

    今回はその逆、JSON文字列をC#のオブジェクトに変換する「デシリアライズ」の方法について詳しく解説していきます。

    たとえば、Web APIから受け取ったJSONレスポンスを自分のアプリケーションで扱いやすくするには、JSON文字列をC#のクラスに変換する必要があります。そのときに使えるのが、JsonSerializer.Deserialize<T>() メソッドです。


    基本のデシリアライズ:JSONをオブジェクトに変換する

    まず、基本の Person クラスから復習です:

    public record Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    このクラスに対応するJSON文字列が以下のようにあるとしましょう。

    {"Id":1,"Name":"Taro"}

    これをC#でデシリアライズするには、以下のように書きます。

    using System.Text.Json;
    
    string json = "{\"Id\":1,\"Name\":\"Taro\"}";
    
    Person? person = JsonSerializer.Deserialize<Person>(json);
    
    Console.WriteLine($"{person?.Id}, {person?.Name}");

    出力:

    1, Taro

    非常に簡単ですね。JSON文字列が有効なフォーマットで、対応するクラスが存在すれば、すぐにデシリアライズできます。


    プロパティ名が小文字(camelCase)のJSONをデシリアライズする

    JavaScriptなど他言語では、プロパティ名が小文字(camelCase)であることが多いです。以下のようなJSON:

    {"id":2,"name":"Jiro"}

    の場合、C#のクラスではPascalCase(Id, Name)で定義しているので、そのままではプロパティにマッピングされません。

    このような場合は、JsonSerializerOptions を使ってネーミングポリシーを設定します。

    var options = new JsonSerializerOptions
    {
        PropertyNameCaseInsensitive = true // 大文字小文字を無視
    };
    
    string json = "{\"id\":2,\"name\":\"Jiro\"}";
    
    var person = JsonSerializer.Deserialize<Person>(json, options);
    
    Console.WriteLine($"{person?.Id}, {person?.Name}");

    出力:

    2, Jiro

    これで、camelCaseなJSON文字列にも柔軟に対応できます。


    配列のデシリアライズ

    複数の Person オブジェクトが入ったJSON配列をデシリアライズすることも可能です。

    以下のJSONを用意します:

    [
      {"Id":1,"Name":"Taro"},
      {"Id":2,"Name":"Jiro"}
    ]

    この配列をデシリアライズしてみましょう。

    string jsonArray = "[{\"Id\":1,\"Name\":\"Taro\"},{\"Id\":2,\"Name\":\"Jiro\"}]";
    
    var people = JsonSerializer.Deserialize<Person[]>(jsonArray);
    
    foreach (var p in people!)
    {
        Console.WriteLine($"{p.Id}, {p.Name}");
    }

    出力:

    1, Taro  
    2, Jiro

    もちろん、List<Person> として受け取ることも可能です:

    List<Person>? peopleList = JsonSerializer.Deserialize<List<Person>>(jsonArray);

    ネストされたオブジェクトのデシリアライズ

    次に、オブジェクトの中に配列があるような、入れ子構造のJSONの例を見てみましょう。

    {
      "GroupId": 1,
      "People": [
        { "Id": 1, "Name": "Taro" },
        { "Id": 2, "Name": "Jiro" }
      ]
    }

    このJSONに対応する Group クラスを以下のように定義します:

    public record Group
    {
        public int GroupId { get; set; }
        public Person[] People { get; set; }
    }

    そして、デシリアライズします。

    string json = @"
    {
      ""GroupId"": 1,
      ""People"": [
        { ""Id"": 1, ""Name"": ""Taro"" },
        { ""Id"": 2, ""Name"": ""Jiro"" }
      ]
    }";
    
    var group = JsonSerializer.Deserialize<Group>(json);
    
    Console.WriteLine($"Group ID: {group?.GroupId}");
    foreach (var p in group?.People!)
    {
        Console.WriteLine($"- {p.Id}, {p.Name}");
    }

    出力:

    Group ID: 1  
    - 1, Taro  
    - 2, Jiro

    このように、入れ子になった構造でも、クラス構造を合わせれば問題なくデシリアライズできます。


    JsonPropertyName属性を使ったマッピング

    シリアライズと同じように、デシリアライズ時にも [JsonPropertyName] 属性を使ってJSONとクラスのプロパティ名をマッピングできます。

    public record Person
    {
        [JsonPropertyName("id")]
        public int Id { get; set; }
    
        [JsonPropertyName("name")]
        public string Name { get; set; }
    }

    JSONが {"id":3,"name":"Saburo"} のようになっていても、以下のようにスムーズに読み取れます:

    string json = "{\"id\":3,\"name\":\"Saburo\"}";
    
    var person = JsonSerializer.Deserialize<Person>(json);
    Console.WriteLine($"{person?.Id}, {person?.Name}");

    デシリアライズの失敗と例外処理

    もしJSONが不正な形式だったり、クラスとマッピングが合っていなかった場合、JsonSerializer.DeserializeJsonException をスローします。

    例:

    string invalidJson = "{\"Id\": \"NotAnInt\", \"Name\": \"Taro\"}";
    
    try
    {
        var person = JsonSerializer.Deserialize<Person>(invalidJson);
    }
    catch (JsonException ex)
    {
        Console.WriteLine("デシリアライズに失敗しました: " + ex.Message);
    }

    nullの扱いに注意

    Deserialize<T> は、デフォルトで null を返す可能性があります。値を安全に扱うためには null チェックを必ず行いましょう。


    パフォーマンスとセキュリティの注意点

    • デシリアライズ対象のJSONは信頼できるソースからのみ受け取るべきです。攻撃者が意図的に破壊的なJSONを送信してくるケースがあります。
    • System.Text.JsonNewtonsoft.Json より高速ですが、一部の特殊なフォーマット(例:JSON Path、型ヒント付きのポリモーフィズム)には対応していない場合があります。

    まとめ

    .NET 8 では System.Text.Json を使うことで、非常にシンプルかつ高速にJSONをデシリアライズできます。

    • JsonSerializer.Deserialize<T>() を使って文字列からオブジェクトを生成
    • プロパティ名がcamelCaseでも JsonSerializerOptionsJsonPropertyName を使えば対応可能
    • 配列やネスト構造も簡単にデシリアライズ可能
    • nullチェックと例外処理も忘れずに

    標準ライブラリだけでここまでできるのは嬉しいですね。JSON APIとやり取りするアプリケーションを作るなら、ぜひ System.Text.Json を活用しましょう!

  • 【C#/.NET8】System.Text.JsonでオブジェクトをJSONにシリアライズする方法

    かつてC#でJSONのシリアライズ(オブジェクトをJSON形式の文字列に変換)を行う際、多くの開発者が利用していたのが Newtonsoft.Json(通称Json.NET)でした。しかし、.NET Core 3.0以降、そして現在の.NET 8では、マイクロソフト公式の System.Text.Json が標準ライブラリとして提供されており、追加のNuGetパッケージなしで高速かつ効率的にJSONの操作が可能となっています。

    本記事では、System.Text.Json を使った基本的なシリアライズの方法から、プロパティ名のカスタマイズ、配列やネストされたオブジェクトのシリアライズまで、順を追って丁寧に解説していきます。


    基本のシリアライズ:クラスをJSONに変換する

    まずは非常にシンプルなクラスを用意してみましょう。

    public record Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    この Person クラスには、IdName という2つのプロパティだけが定義されています。これをインスタンス化し、JSON文字列にシリアライズしてみます。

    using System.Text.Json;
    
    var p1 = new Person { Id = 1, Name = "Taro" };
    var jsonP1 = JsonSerializer.Serialize<Person>(p1);
    
    Console.WriteLine(jsonP1);

    出力されるJSON文字列は以下の通りです:

    {"Id":1,"Name":"Taro"}

    とても簡単ですね!JsonSerializer.Serialize メソッドにオブジェクトを渡すだけで、自動的にプロパティがJSON形式に変換されます。


    プロパティ名をカスタマイズする:小文字に変えたい場合

    JavaScriptとやりとりする場合など、JSONのプロパティ名を小文字から始めたいケースは多いでしょう。その場合、System.Text.Json.Serialization.JsonPropertyName 属性を使えば、出力されるプロパティ名を指定できます。

    以下のように Person クラスを修正します:

    using System.Text.Json.Serialization;
    
    public record Person
    {
        [JsonPropertyName("id")]
        public int Id { get; set; }
    
        [JsonPropertyName("name")]
        public string Name { get; set; }
    }
    

    そして、次のようにシリアライズしてみましょう。

    var p4 = new Person { Id = 4, Name = "Shiro" };
    var jsonP4 = JsonSerializer.Serialize<Person>(p4);
    
    Console.WriteLine(jsonP4);

    出力:

    {"id":4,"name":"Shiro"}

    このように、C# の PascalCase(先頭大文字)を JSON の camelCase(先頭小文字)に柔軟に変換できます。


    配列のシリアライズ

    次は、複数の Person インスタンスを配列にしてシリアライズする例です。

    var p2 = new Person { Id = 2, Name = "Jiro" };
    var people = new Person[] { p1, p2 };
    
    var jsonArray = JsonSerializer.Serialize<Person[]>(people);
    Console.WriteLine(jsonArray);

    出力:

    [{"Id":1,"Name":"Taro"},{"Id":2,"Name":"Jiro"}]

    このように、配列やリストをそのまま渡すだけで、複数のオブジェクトが正しくJSONの配列として出力されます。


    ネストされたオブジェクトのシリアライズ

    もちろん、オブジェクトの中にさらにオブジェクトや配列を含む場合も、System.Text.Json はしっかり対応しています。

    例えば、Group というクラスを用意して、その中に Person[] を持たせるケースを考えてみましょう。

    public record Group
    {
        public int GroupId { get; set; }
        public Person[] People { get; set; }
    }

    Group インスタンスを作成してみます。

    var group = new Group
    {
        GroupId = 1,
        People = new Person[] { p1, p2 }
    };
    
    var jsonGroup = JsonSerializer.Serialize<Group>(group);
    Console.WriteLine(jsonGroup);

    出力:

    {"GroupId":1,"People":[{"Id":1,"Name":"Taro"},{"Id":2,"Name":"Jiro"}]}

    プロパティ名をカスタマイズしたい場合は、先ほどと同じく JsonPropertyName を使えばOKです。


    その他の便利な設定オプション

    JsonSerializerOptions を使うと、シリアライズ時の挙動をさらにカスタマイズできます。

    例:camelCaseに自動変換したい場合

    var options = new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = true // 整形して見やすく
    };
    
    var jsonIndented = JsonSerializer.Serialize(p1, options);
    Console.WriteLine(jsonIndented);

    出力:

    {
      "id": 1,
      "name": "Taro"
    }

    このように、JsonSerializerOptions を使えば、クラス側に [JsonPropertyName] をつけなくても一括でネーミングポリシーを変更できます。


    Newtonsoft.Json との違い

    機能Newtonsoft.JsonSystem.Text.Json
    標準ライブラリ×(別途NuGet必要)○(.NET Core 3.0〜)
    パフォーマンス普通高速
    カスタマイズ性非常に高い徐々に充実中
    JSON Schema対応×
    デフォルトでの循環参照検出×○(例外で検出)

    .NET 8 を使用しているなら、特に理由がなければ System.Text.Json を積極的に使っていくのが良いでしょう。


    まとめ

    .NET 8以降では、標準ライブラリである System.Text.Json を使うことで、高速かつシンプルにJSONのシリアライズが可能です。

    • JsonSerializer.Serialize を使えば簡単に変換できる
    • JsonPropertyName 属性でプロパティ名を自由に指定可能
    • 配列、ネストされたオブジェクトも問題なし
    • JsonSerializerOptions でネーミングポリシーや整形出力も可能

    普段の業務や個人開発においても、外部ライブラリに依存せずに軽量な構成を実現できます。今後は System.Text.Json を積極的に活用していきましょう!

  • [C#, .NET8] System.Text.Jsonの使い方

    普通のクラスをJSONにシリアライズして、その後、デシリアライズしてみましょう。

    使うクラスはこんな感じで適当なものを用意しました。

    public record Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    
    var p1 = new Person{ Id = 1, Name="Taro" };
    var p2 = new Person{ Id = 2, Name="Jiro" };

    PersonクラスにIdNameだけを持たせておいて、これを操作していきます。

    次の例では、単にJSONにシリアライズして、その後、そのまま文字列をデシリアライズするというものです。

    オブジェクト単体で

    using System.Text.Json;
    
    var jsonP1 = JsonSerializer.Serialize<Person>(p1);
    Console.WriteLine(jsonP1);
    var p3 = JsonSerializer.Deserialize<Person>(jsonP1);
    Console.WriteLine($"person:{p3.Id}, {p3.Name}");
    {"Id":1,"Name":"Taro"}
    person:1, Taro

    特に何も指定が無ければ、JSONの属性名は、C#と同じものがつきます。

    JSONなので、Javascript的に先頭を小文字にしたい場合があると思います。その場合は、System.Text.Json.Serialization を読み込んであげて、各プロパティにJsonPropertyName属性を付けます。引数に文字列を入れれば、任意のプロパティ名を付けられます。

    using System.Text.Json.Serialization;
    public record Person
    {
        [JsonPropertyName("id")]
        public int Id { get; set; }
        [JsonPropertyName("name")]
        public string Name { get; set; }
    }
    
    var p4 = new Person{ Id = 4, Name="Shiro" };
    
    var jsonP4 = JsonSerializer.Serialize<Person>(p4);
    Console.WriteLine(jsonP4);
    var p5 = JsonSerializer.Deserialize<Person>(jsonP4);
    Console.WriteLine($"person:{p5.Id}, {p5.Name}");
    {"id":4,"name":"Shiro"}
    person:4, Shiro

    配列で

    var jsonPArray = JsonSerializer.Serialize<Person[]>([p1,p2]);
    Console.WriteLine(jsonPArray);
    var pArray = JsonSerializer.Deserialize<Person[]>(jsonPArray);
    foreach(var p in pArray)
    {
        Console.WriteLine($"person:{p.Id}, {p.Name}");
    }
    [{"Id":1,"Name":"Taro"},{"Id":2,"Name":"Jiro"}]
    person:1, Taro
    person:2, Jiro

    配列の場合も同様です。配列を入れたら、配列のJSONが返ってきて、デシリアライズもできます。

    オブジェクトで

    もちろん入れ子構造になっている場合もきちんとシリアライズして、デシリアライズできます。

    下記の例では、オブジェクトの中に配列を持っている場合を示しています。

    public record Group
    {
        public int GroupId { get; set; }
        public Person[] People { get; set; }
    }
    
    var group = new Group{ GroupId=1, People = [p1, p2] };
    
    var jsonG = JsonSerializer.Serialize<Group>(group);
    Console.WriteLine(jsonG);
    var g = JsonSerializer.Deserialize<Group>(jsonG);
    Console.WriteLine($"group:{g.GroupId}");
    foreach(var p in g.People)
    {
        Console.WriteLine($"person:{p.Id}, {p.Name}");
    }
    {"GroupId":1,"People":[{"Id":1,"Name":"Taro"},{"Id":2,"Name":"Jiro"}]}
    group:1
    person:1, Taro
    person:2, Jiro

    以上です。