ファイル

【C#】ListをJSONへ高速変換する方法:System.Text.JsonとNewtonsoft.Jsonの比較

C#でListをJSONへ変換するなら標準のJsonSerializer.Serializeが最速かつ追加依存なしで済みます。

JsonSerializerOptionsを添えればインデント整形やUnicodeエスケープ抑制も簡単に行え、日本語データも安全です。

旧来プロジェクトや細かな属性制御が必要な場面ではJsonConvert.SerializeObjectが依然有力な選択肢となります。

基本的な変換フロー

C#でList<T>をJSON形式に変換する際、代表的な方法として標準ライブラリのSystem.Text.Jsonと外部ライブラリのNewtonsoft.Jsonがあります。

ここでは、それぞれのライブラリを使った基本的なシリアライズ手順を具体的なコード例とともに解説します。

System.Text.Jsonによるシリアライズ

List<string>の最短手順

System.Text.Jsonは.NET Core 3.0以降で標準搭載されている高速なJSONシリアライズライブラリです。

List<string>のような単純なリストをJSON文字列に変換するのは非常に簡単です。

以下のサンプルコードは、文字列のリストをJSONに変換し、コンソールに出力する最短の手順を示しています。

using System;
using System.Collections.Generic;
using System.Text.Json;
class Program
{
    static void Main()
    {
        // フルーツ名のリストを作成
        var fruits = new List<string> { "Apple", "Banana", "Cherry" };
        // List<string>をJSON文字列に変換
        string jsonString = JsonSerializer.Serialize(fruits);
        // 結果を表示
        Console.WriteLine(jsonString);
    }
}
["Apple","Banana","Cherry"]

このように、JsonSerializer.SerializeメソッドにList<string>を渡すだけで、簡単にJSON配列形式の文字列が得られます。

特別な設定は不要で、デフォルトの動作で十分なケースが多いです。

エンコードを保持した日本語変換

日本語などの非ASCII文字を含む文字列をJSONに変換すると、デフォルトではUnicodeエスケープ(\uXXXX形式)されてしまうことがあります。

これを防ぎ、元の日本語文字をそのままJSONに出力したい場合は、JsonSerializerOptionsEncoderプロパティを設定します。

以下のコードは、日本語のリストをエスケープせずにそのままJSONに変換し、さらに見やすいようにインデント付きで出力する例です。

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Unicode;
class Program
{
    static void Main()
    {
        // 日本語の果物名リスト
        var fruits = new List<string> { "りんご", "バナナ", "さくらんぼ" };
        // Unicode文字をエスケープせずに出力するオプションを設定
        var options = new JsonSerializerOptions
        {
            Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
            WriteIndented = true // インデント付きで見やすくする
        };
        // JSONに変換
        string jsonString = JsonSerializer.Serialize(fruits, options);
        // 結果を表示
        Console.WriteLine(jsonString);
    }
}
[
  "りんご",
  "バナナ",
  "さくらんぼ"
]

このように、JavaScriptEncoder.Create(UnicodeRanges.All)を指定することで、すべてのUnicode文字をエスケープせずにそのままJSONに含められます。

日本語を含むデータを扱う場合は、この設定を使うと可読性が向上します。

Newtonsoft.Jsonによるシリアライズ

List<string>の最短手順

Newtonsoft.Json(別名Json.NET)は、C#で長く使われているJSONライブラリで、多機能かつ柔軟なシリアライズが可能です。

NuGetからインストールして利用します。

まずは、List<string>をJSONに変換する最短のコード例です。

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
class Program
{
    static void Main()
    {
        // フルーツ名のリストを作成
        var fruits = new List<string> { "Apple", "Banana", "Cherry" };
        // List<string>をJSON文字列に変換
        string jsonString = JsonConvert.SerializeObject(fruits);
        // 結果を表示
        Console.WriteLine(jsonString);
    }
}
["Apple","Banana","Cherry"]

JsonConvert.SerializeObjectにリストを渡すだけで、簡単にJSON配列形式の文字列が得られます。

Newtonsoft.Jsonは多くのプロジェクトで使われており、互換性も高いです。

Formatting指定での整形出力

Newtonsoft.Jsonでは、SerializeObjectの第2引数にFormatting.Indentedを指定することで、インデント付きの整形されたJSONを出力できます。

日本語を含む場合も同様に使えます。

以下は日本語のリストをインデント付きでJSONに変換する例です。

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
class Program
{
    static void Main()
    {
        // 日本語の果物名リスト
        var fruits = new List<string> { "りんご", "バナナ", "さくらんぼ" };
        // インデント付きでJSONに変換
        string jsonString = JsonConvert.SerializeObject(fruits, Formatting.Indented);
        // 結果を表示
        Console.WriteLine(jsonString);
    }
}
[
  "りんご",
  "バナナ",
  "さくらんぼ"
]

Formatting.Indentedを指定するだけで、改行やスペースが挿入されて見やすいJSONになります。

Newtonsoft.Jsonは日本語の文字列も特別な設定なしでそのまま出力されるため、エンコードの心配はほとんどありません。

以上が、System.Text.JsonNewtonsoft.Jsonを使ったList<T>の基本的なJSON変換の流れです。

どちらも簡単に使えますが、用途や環境に応じて使い分けることがポイントです。

オプションによる挙動の調整

System.Text.JsonのJsonSerializerOptions

インデント設定

System.Text.JsonでJSONを見やすく整形するには、JsonSerializerOptionsWriteIndentedプロパティをtrueに設定します。

これにより、改行やスペースが挿入され、階層構造がわかりやすくなります。

using System;
using System.Collections.Generic;
using System.Text.Json;
class Program
{
    static void Main()
    {
        var fruits = new List<string> { "Apple", "Banana", "Cherry" };
        var options = new JsonSerializerOptions
        {
            WriteIndented = true // インデントを有効にする
        };
        string jsonString = JsonSerializer.Serialize(fruits, options);
        Console.WriteLine(jsonString);
    }
}
[
  "Apple",
  "Banana",
  "Cherry"
]

この設定は、デバッグ時やログ出力でJSONの構造を確認したい場合に便利です。

逆に、ファイルサイズを小さくしたい場合はfalseにしてコンパクトなJSONを生成します。

Unicodeエスケープ抑制

デフォルトでは、System.Text.Jsonは非ASCII文字をUnicodeエスケープ(\uXXXX形式)で出力します。

日本語などの文字をそのまま表示したい場合は、EncoderプロパティにJavaScriptEncoder.Create(UnicodeRanges.All)を指定します。

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Unicode;
class Program
{
    static void Main()
    {
        var fruits = new List<string> { "りんご", "バナナ", "さくらんぼ" };
        var options = new JsonSerializerOptions
        {
            Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), // Unicodeエスケープを抑制
            WriteIndented = true
        };
        string jsonString = JsonSerializer.Serialize(fruits, options);
        Console.WriteLine(jsonString);
    }
}
[
  "りんご",
  "バナナ",
  "さくらんぼ"
]

この設定を使うと、JSONの可読性が向上し、特に日本語を含むデータを扱う際に便利です。

Null値の制御

System.Text.Jsonでは、シリアライズ時にnullのプロパティを出力するかどうかをDefaultIgnoreConditionプロパティで制御できます。

JsonIgnoreCondition.WhenWritingNullを指定すると、nullの値はJSONに含まれません。

using System;
using System.Text.Json;
class Person
{
    public string Name { get; set; }
    public string? Nickname { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { Name = "山田太郎", Nickname = null };
        var options = new JsonSerializerOptions
        {
            WriteIndented = true,
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
        };
        string jsonString = JsonSerializer.Serialize(person, options);
        Console.WriteLine(jsonString);
    }
}
{
  "Name": "山田太郎"
}

Nicknamenullのため、JSONには含まれていません。

nullも含めたい場合は、このオプションを設定しなければnullも出力されます。

Newtonsoft.Jsonの設定プロパティ

FormattingとNullValueHandling

Newtonsoft.Jsonでは、Formatting列挙体を使ってインデントの有無を指定します。

Formatting.Indentedで整形出力、Formatting.Noneでコンパクト出力です。

また、NullValueHandlingプロパティでnull値の出力制御が可能です。

NullValueHandling.Ignoreを指定すると、nullのプロパティはJSONに含まれません。

using System;
using Newtonsoft.Json;
class Person
{
    public string Name { get; set; }
    public string Nickname { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { Name = "山田太郎", Nickname = null };
        var settings = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented, // インデント付き出力
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            NullValueHandling = NullValueHandling.Ignore // null値を無視
        };
        string jsonString = JsonConvert.SerializeObject(person, settings);
        Console.WriteLine(jsonString);
    }
}
{
  "Name": "山田太郎"
}

Nicknamenullのため、JSONに含まれていません。

NullValueHandling.Includeを指定するとnullも出力されます。

DefaultValueHandlingとDateFormatString

DefaultValueHandlingは、プロパティのデフォルト値を持つ場合にシリアライズするかどうかを制御します。

例えば、DefaultValueHandling.Ignoreを指定すると、デフォルト値のプロパティはJSONに含まれません。

DateFormatStringは、日付型のプロパティをシリアライズする際のフォーマットを指定できます。

using System;
using Newtonsoft.Json;
class Event
{
    public string Title { get; set; }
    public DateTime Date { get; set; }
    public int Count { get; set; } = 0;
}
class Program
{
    static void Main()
    {
        var evt = new Event
        {
            Title = "セミナー",
            Date = new DateTime(2024, 6, 1),
            Count = 0 // デフォルト値
        };
        var settings = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented,
            DefaultValueHandling = DefaultValueHandling.Ignore, // デフォルト値は無視
            DateFormatString = "yyyy-MM-dd" // 日付フォーマット指定
        };
        string jsonString = JsonConvert.SerializeObject(evt, settings);
        Console.WriteLine(jsonString);
    }
}
{
  "Title": "セミナー",
  "Date": "2024-06-01"
}

Countはデフォルト値の0なのでJSONに含まれていません。

DateFormatStringで日付がyyyy-MM-dd形式で出力されています。

これらの設定は、JSONのサイズ削減やフォーマット統一に役立ちます。

ネストされた型を含むList<T>の扱い

クラスオブジェクトのシリアライズ

System.Text.Jsonでのネスト構造

System.Text.Jsonは、List<T>の中に複雑なクラスオブジェクトが含まれていても、問題なくシリアライズできます。

クラスのプロパティは自動的にJSONのネスト構造として表現されます。

以下は、Personクラスを要素とするリストをJSONに変換する例です。

using System;
using System.Collections.Generic;
using System.Text.Json;
class Program
{
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public Address HomeAddress { get; set; }
    }
    class Address
    {
        public string City { get; set; }
        public string Street { get; set; }
    }
    static void Main()
    {
        var people = new List<Person>
        {
            new Person
            {
                Name = "山田太郎",
                Age = 30,
                HomeAddress = new Address { City = "東京", Street = "千代田区1-1" }
            },
            new Person
            {
                Name = "鈴木花子",
                Age = 25,
                HomeAddress = new Address { City = "大阪", Street = "北区2-2" }
            }
        };
        var options = new JsonSerializerOptions
        {
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            WriteIndented = true
        };
        string jsonString = JsonSerializer.Serialize(people, options);
        Console.WriteLine(jsonString);
    }
}
[
  {
    "Name": "山田太郎",
    "Age": 30,
    "HomeAddress": {
      "City": "東京",
      "Street": "千代田区1-1"
    }
  },
  {
    "Name": "鈴木花子",
    "Age": 25,
    "HomeAddress": {
      "City": "大阪",
      "Street": "北区2-2"
    }
  }
]

このように、Personクラスの中にAddressクラスがネストされていても、System.Text.Jsonは自動的に階層構造をJSONに反映します。

特別な設定は不要で、標準的なPOCO(Plain Old CLR Object)であれば問題なくシリアライズできます。

Newtonsoft.Jsonでのネスト構造

Newtonsoft.Jsonも同様に、ネストされたクラスオブジェクトを含むList<T>を簡単にシリアライズできます。

JsonConvert.SerializeObjectにリストを渡すだけで、ネスト構造がJSONに反映されます。

以下は先ほどと同じ構造の例です。

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
class Program
{
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public Address HomeAddress { get; set; }
    }
    class Address
    {
        public string City { get; set; }
        public string Street { get; set; }
    }
    static void Main()
    {
        var people = new List<Person>
        {
            new Person
            {
                Name = "山田太郎",
                Age = 30,
                HomeAddress = new Address { City = "東京", Street = "千代田区1-1" }
            },
            new Person
            {
                Name = "鈴木花子",
                Age = 25,
                HomeAddress = new Address { City = "大阪", Street = "北区2-2" }
            }
        };
        string jsonString = JsonConvert.SerializeObject(people, Formatting.Indented);
        Console.WriteLine(jsonString);
    }
}
[
  {
    "Name": "山田太郎",
    "Age": 30,
    "HomeAddress": {
      "City": "東京",
      "Street": "千代田区1-1"
    }
  },
  {
    "Name": "鈴木花子",
    "Age": 25,
    "HomeAddress": {
      "City": "大阪",
      "Street": "北区2-2"
    }
  }
]

Newtonsoft.Jsonは多機能でカスタマイズ性も高いですが、基本的なネスト構造のシリアライズは非常にシンプルに行えます。

Formatting.Indentedを指定して見やすく整形しています。

Dictionary入りListの変換

List<T>の要素にDictionary<TKey, TValue>が含まれている場合も、両ライブラリで問題なくシリアライズできます。

DictionaryはJSONのオブジェクトとして表現されます。

以下は、List<Dictionary<string, string>>をJSONに変換する例です。

using System;
using System.Collections.Generic;
using System.Text.Json;
class Program
{
    static void Main()
    {
        var listOfDicts = new List<Dictionary<string, string>>
        {
            new Dictionary<string, string>
            {
                { "Name", "山田太郎" },
                { "City", "東京" }
            },
            new Dictionary<string, string>
            {
                { "Name", "鈴木花子" },
                { "City", "大阪" }
            }
        };
        var options = new JsonSerializerOptions
        {
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            WriteIndented = true
        };
        string jsonString = JsonSerializer.Serialize(listOfDicts, options);
        Console.WriteLine(jsonString);
    }
}
[
  {
    "Name": "山田太郎",
    "City": "東京"
  },
  {
    "Name": "鈴木花子",
    "City": "大阪"
  }
]

同様に、Newtonsoft.Jsonでも同じようにシリアライズできます。

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
class Program
{
    static void Main()
    {
        var listOfDicts = new List<Dictionary<string, string>>
        {
            new Dictionary<string, string>
            {
                { "Name", "山田太郎" },
                { "City", "東京" }
            },
            new Dictionary<string, string>
            {
                { "Name", "鈴木花子" },
                { "City", "大阪" }
            }
        };
        string jsonString = JsonConvert.SerializeObject(listOfDicts, Formatting.Indented);
        Console.WriteLine(jsonString);
    }
}
[
  {
    "Name": "山田太郎",
    "City": "東京"
  },
  {
    "Name": "鈴木花子",
    "City": "大阪"
  }
]

DictionaryのキーがJSONのプロパティ名、値が対応する値として出力されるため、動的なキー・値の組み合わせを扱う場合に便利です。

List<T>の中にDictionaryが混在していても、どちらのライブラリでもスムーズにJSON変換が可能です。

パフォーマンス検証ポイント

シリアライズ速度の測定観点

JSONシリアライズの速度は、アプリケーションのレスポンスや処理時間に大きく影響します。

速度を正確に測定するには、以下のポイントに注意します。

  • 対象データのサイズと構造

小規模な単純リストと、大規模かつネストされた複雑なオブジェクトでは処理時間が大きく異なります。

実際の利用シナリオに近いデータを用意することが重要です。

  • ウォームアップの実施

JITコンパイルや初期化処理の影響を避けるため、測定前に数回シリアライズを実行してウォームアップします。

  • 繰り返し回数の設定

複数回のシリアライズを行い、平均時間を算出することで安定した結果を得られます。

  • 計測方法

System.Diagnostics.Stopwatchを使い、開始から終了までの経過時間を計測します。

高精度な計測が可能です。

  • 同期・非同期の違い

非同期APIを使う場合は、async/awaitのオーバーヘッドも考慮します。

以下は速度測定の簡単な例です。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
class Program
{
    static void Main()
    {
        var list = new List<string>();
        for (int i = 0; i < 10000; i++)
        {
            list.Add("Item " + i);
        }
        var options = new JsonSerializerOptions { WriteIndented = false };
        // ウォームアップ
        JsonSerializer.Serialize(list, options);
        var sw = Stopwatch.StartNew();
        for (int i = 0; i < 100; i++)
        {
            JsonSerializer.Serialize(list, options);
        }
        sw.Stop();
        Console.WriteLine($"平均シリアライズ時間: {sw.ElapsedMilliseconds / 100.0} ms");
    }
}

このように、実際のデータ量や繰り返し回数を調整して測定します。

バッファ割り当てとメモリ効率

シリアライズ処理はメモリの割り当て頻度やサイズにも影響を受けます。

特に大量データを扱う場合、バッファの使い方がパフォーマンスに直結します。

  • System.Text.Jsonの特徴

System.Text.Jsonは内部でUtf8JsonWriterを使い、バッファを効率的に管理しています。

バッファサイズを指定できるため、大きなデータを一括で処理する際にメモリ割り当てを抑えられます。

  • Newtonsoft.Jsonの特徴

Newtonsoft.JsonStringWriterJsonTextWriterを使い、文字列バッファを動的に拡張します。

大量データではバッファの再割り当てが多くなり、メモリ使用量が増えることがあります。

  • バッファプールの活用

System.Text.Jsonは.NETのArrayPool<byte>を活用し、バッファの再利用を促進しています。

これによりGC負荷が軽減されます。

  • メモリプロファイリング

実際のアプリケーションでは、メモリプロファイラーを使って割り当て状況を確認し、ボトルネックを特定します。

ファイルサイズの比較

生成されるJSONのファイルサイズも重要な指標です。

ファイルサイズが大きいと、ネットワーク転送やディスクI/Oに影響します。

  • インデントの有無

インデント付き(Pretty Print)にすると可読性は上がりますが、スペースや改行が増えファイルサイズが大きくなります。

サイズを抑えたい場合はインデントを無効にします。

  • Unicodeエスケープの影響

System.Text.Jsonはデフォルトで非ASCII文字をUnicodeエスケープします。

これによりファイルサイズが増えることがあります。

エスケープを抑制するとサイズが小さくなる場合があります。

  • デフォルト値やnullの除外

不要なnullやデフォルト値を除外する設定を使うと、JSONのサイズを削減できます。

  • 圧縮の検討

JSONファイルをgzipなどで圧縮することで、転送時のサイズを大幅に減らせます。

以下は、同じデータをSystem.Text.JsonNewtonsoft.Jsonでシリアライズし、インデントの有無でファイルサイズを比較した例です。

ライブラリインデントファイルサイズ(バイト)
System.Text.Jsonなし12345
System.Text.Jsonあり23456
Newtonsoft.Jsonなし12500
Newtonsoft.Jsonあり23600

(※数値は例示です。

実際のサイズはデータ内容や設定により異なります)

このように、用途に応じてインデントやエスケープ設定を調整し、パフォーマンスとファイルサイズのバランスを取ることが重要です。

エラーと注意事項

循環参照が存在するList

List<T>の中に循環参照がある場合、シリアライズ時に無限ループや例外が発生することがあります。

例えば、オブジェクトAがオブジェクトBを参照し、オブジェクトBが再びオブジェクトAを参照している場合です。

System.Text.Jsonの場合

System.Text.Jsonはデフォルトで循環参照を検出しません。

そのため、循環参照があるとJsonExceptionがスローされます。

これを回避するには、JsonSerializerOptionsReferenceHandlerReferenceHandler.Preserveを設定します。

これにより、循環参照を特殊な$id$refで表現し、無限ループを防ぎます。

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
class Program
{
    class Node
    {
        public string Name { get; set; }
        public Node Next { get; set; }
    }
    static void Main()
    {
        var node1 = new Node { Name = "Node1" };
        var node2 = new Node { Name = "Node2" };
        node1.Next = node2;
        node2.Next = node1; // 循環参照
        var options = new JsonSerializerOptions
        {
            WriteIndented = true,
            ReferenceHandler = ReferenceHandler.Preserve
        };
        string jsonString = JsonSerializer.Serialize(node1, options);
        Console.WriteLine(jsonString);
    }
}
{
  "$id": "1",
  "Name": "Node1",
  "Next": {
    "$id": "2",
    "Name": "Node2",
    "Next": {
      "$ref": "1"
    }
  }
}

このように、循環参照を安全にシリアライズできます。

Newtonsoft.Jsonの場合

Newtonsoft.Jsonはデフォルトで循環参照を検出し、例外をスローします。

回避するには、JsonSerializerSettingsReferenceLoopHandlingReferenceLoopHandling.IgnoreReferenceLoopHandling.Serializeに設定します。

using System;
using Newtonsoft.Json;
class Program
{
    class Node
    {
        public string Name { get; set; }
        public Node Next { get; set; }
    }
    static void Main()
    {
        var node1 = new Node { Name = "Node1" };
        var node2 = new Node { Name = "Node2" };
        node1.Next = node2;
        node2.Next = node1; // 循環参照
        var settings = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented,
            ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
            PreserveReferencesHandling = PreserveReferencesHandling.Objects
        };
        string jsonString = JsonConvert.SerializeObject(node1, settings);
        Console.WriteLine(jsonString);
    }
}
{
  "$id": "1",
  "Name": "Node1",
  "Next": {
    "$id": "2",
    "Name": "Node2",
    "Next": {
      "$ref": "1"
    }
  }
}

ReferenceLoopHandling.Ignoreを指定すると、循環参照のプロパティは無視されますが、データの一部が欠落する可能性があるため注意が必要です。

プロパティ名の大文字小文字差異

JSONのキー名は大文字小文字を区別しますが、C#のプロパティ名は慣習的に大文字で始まります。

シリアライズ・デシリアライズ時に大文字小文字の違いが問題になることがあります。

System.Text.Jsonの場合

デフォルトでは、System.Text.JsonはC#のプロパティ名をそのままJSONのキー名として使用します。

デシリアライズ時も大文字小文字を区別しますが、JsonSerializerOptionsPropertyNameCaseInsensitivetrueに設定すると、大文字小文字を無視してマッピングできます。

using System;
using System.Text.Json;
class Person
{
    public string FirstName { get; set; }
}
class Program
{
    static void Main()
    {
        string json = "{\"firstname\":\"Taro\"}";
        var options = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        };
        var person = JsonSerializer.Deserialize<Person>(json, options);
        Console.WriteLine(person.FirstName);
    }
}
Taro

Newtonsoft.Jsonの場合

Newtonsoft.Jsonはデフォルトで大文字小文字を区別しません。

JSONのキー名がfirstnameでも、C#のFirstNameプロパティにマッピングされます。

特別な設定は不要です。

非公開メンバーの取り扱い

シリアライズ対象のクラスに非公開privateprotectedのメンバーがある場合、両ライブラリでの扱いが異なります。

System.Text.Jsonの場合

System.Text.Jsonはデフォルトで公開されているプロパティのみをシリアライズ対象とします。

非公開メンバーは無視されます。

非公開メンバーをシリアライズしたい場合は、カスタムコンバーターを作成する必要があります。

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
class Person
{
    public string Name { get; set; }
    private int Age { get; set; } = 30;
    public int GetAge() => Age;
}
class Program
{
    static void Main()
    {
        var person = new Person { Name = "Taro" };
        var options = new JsonSerializerOptions { WriteIndented = true };
        string jsonString = JsonSerializer.Serialize(person, options);
        Console.WriteLine(jsonString);
    }
}
{
  "Name": "Taro"
}

Ageは非公開なのでJSONに含まれていません。

Newtonsoft.Jsonの場合

Newtonsoft.Jsonはデフォルトで公開プロパティのみをシリアライズしますが、JsonProperty属性を使うか、DefaultContractResolverをカスタマイズすることで非公開メンバーもシリアライズ可能です。

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
class Person
{
    public string Name { get; set; }
    [JsonProperty]
    private int Age { get; set; } = 30;
    public int GetAge() => Age;
}
class Program
{
    static void Main()
    {
        var person = new Person { Name = "Taro" };
        var settings = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented
        };
        string jsonString = JsonConvert.SerializeObject(person, settings);
        Console.WriteLine(jsonString);
    }
}
{
  "Name": "Taro",
  "Age": 30
}

[JsonProperty]属性を付けることで、非公開のAgeプロパティもシリアライズされます。

カスタマイズ性が高いため、細かい制御が必要な場合に便利です。

属性による制御テクニック

JsonPropertyNameとJsonProperty

JSONのキー名をC#のプロパティ名と異なる名前にしたい場合、System.Text.JsonではJsonPropertyName属性を使い、Newtonsoft.JsonではJsonProperty属性を使います。

これにより、シリアライズ・デシリアライズ時のキー名を自由にカスタマイズできます。

System.Text.JsonのJsonPropertyName

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
class Person
{
    [JsonPropertyName("first_name")]
    public string FirstName { get; set; }
    [JsonPropertyName("age_years")]
    public int Age { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { FirstName = "Taro", Age = 30 };
        var options = new JsonSerializerOptions { WriteIndented = true };
        string jsonString = JsonSerializer.Serialize(person, options);
        Console.WriteLine(jsonString);
    }
}
{
  "first_name": "Taro",
  "age_years": 30
}

このように、JsonPropertyName属性でJSONのキー名を指定できます。

Newtonsoft.JsonのJsonProperty

using System;
using Newtonsoft.Json;
class Person
{
    [JsonProperty("first_name")]
    public string FirstName { get; set; }
    [JsonProperty("age_years")]
    public int Age { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { FirstName = "Taro", Age = 30 };
        string jsonString = JsonConvert.SerializeObject(person, Formatting.Indented);
        Console.WriteLine(jsonString);
    }
}
{
  "first_name": "Taro",
  "age_years": 30
}

JsonProperty属性を使うことで、同様にキー名をカスタマイズできます。

JsonIgnoreとJsonInclude

特定のプロパティをシリアライズ・デシリアライズから除外したい場合、System.Text.JsonではJsonIgnore属性を使います。

逆に、非公開メンバーを含めたい場合はJsonInclude属性を使います。

Newtonsoft.JsonでもJsonIgnore属性が利用可能です。

System.Text.JsonのJsonIgnore

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
class Person
{
    public string Name { get; set; }
    [JsonIgnore]
    public int Age { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { Name = "Taro", Age = 30 };
        var options = new JsonSerializerOptions { WriteIndented = true };
        string jsonString = JsonSerializer.Serialize(person, options);
        Console.WriteLine(jsonString);
    }
}
{
  "Name": "Taro"
}

AgeプロパティはJsonIgnoreで除外され、JSONに含まれません。

System.Text.JsonのJsonInclude

非公開のフィールドやプロパティをシリアライズ対象に含めたい場合、JsonIncludeを使います。

これは主にprivateprotectedの読み取り専用プロパティに適用します。

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
class Person
{
    public string Name { get; set; }
    [JsonInclude]
    public int Age { get; private set; }
    public Person(int age)
    {
        Age = age;
    }
}
class Program
{
    static void Main()
    {
        var person = new Person(30) { Name = "Taro" };
        var options = new JsonSerializerOptions { WriteIndented = true };
        string jsonString = JsonSerializer.Serialize(person, options);
        Console.WriteLine(jsonString);
    }
}
{
  "Name": "Taro",
  "Age": 30
}

Newtonsoft.JsonのJsonIgnore

using System;
using Newtonsoft.Json;
class Person
{
    public string Name { get; set; }
    [JsonIgnore]
    public int Age { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { Name = "Taro", Age = 30 };
        string jsonString = JsonConvert.SerializeObject(person, Formatting.Indented);
        Console.WriteLine(jsonString);
    }
}
{
  "Name": "Taro"
}

Newtonsoft.JsonではJsonIncludeに相当する属性はありませんが、非公開メンバーをシリアライズしたい場合はカスタム設定やJsonProperty属性を使います。

JsonConverterのカスタム実装

標準のシリアライズ方法では対応できない特殊な型やフォーマットを扱う場合、カスタムコンバーターを実装して制御できます。

System.Text.JsonNewtonsoft.Jsonの両方でカスタムコンバーターを作成可能です。

System.Text.Jsonのカスタムコンバーター例

以下は、DateTimeyyyyMMdd形式の文字列でシリアライズ・デシリアライズするカスタムコンバーターの例です。

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
class DateOnlyConverter : JsonConverter<DateTime>
{
    private const string Format = "yyyyMMdd";
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string s = reader.GetString();
        return DateTime.ParseExact(s, Format, null);
    }
    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString(Format));
    }
}
class Event
{
    public string Name { get; set; }
    [JsonConverter(typeof(DateOnlyConverter))]
    public DateTime Date { get; set; }
}
class Program
{
    static void Main()
    {
        var evt = new Event { Name = "セミナー", Date = new DateTime(2024, 6, 1) };
        var options = new JsonSerializerOptions
        {
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            WriteIndented = true
        };
        string jsonString = JsonSerializer.Serialize(evt, options);
        Console.WriteLine(jsonString);
        var deserialized = JsonSerializer.Deserialize<Event>(jsonString, options);
        Console.WriteLine(deserialized.Date.ToString("yyyy-MM-dd"));
    }
}
{
  "Name": "セミナー",
  "Date": "20240601"
}
2024-06-01

Newtonsoft.Jsonのカスタムコンバーター例

同様に、Newtonsoft.Jsonでもカスタムコンバーターを作成できます。

using System;
using Newtonsoft.Json;
class DateOnlyConverter : JsonConverter
{
    private const string Format = "yyyyMMdd";
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var date = (DateTime)value;
        writer.WriteValue(date.ToString(Format));
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        string s = (string)reader.Value;
        return DateTime.ParseExact(s, Format, null);
    }
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(DateTime);
    }
}
class Event
{
    public string Name { get; set; }
    [JsonConverter(typeof(DateOnlyConverter))]
    public DateTime Date { get; set; }
}
class Program
{
    static void Main()
    {
        var evt = new Event { Name = "セミナー", Date = new DateTime(2024, 6, 1) };
        string jsonString = JsonConvert.SerializeObject(evt, Formatting.Indented);
        Console.WriteLine(jsonString);
        var deserialized = JsonConvert.DeserializeObject<Event>(jsonString);
        Console.WriteLine(deserialized.Date.ToString("yyyy-MM-dd"));
    }
}
{
  "Name": "セミナー",
  "Date": "20240601"
}
2024-06-01

カスタムコンバーターを使うことで、独自のフォーマットや特殊な型のシリアライズ・デシリアライズを柔軟に実装できます。

asyncシリアライズとストリーム出力

System.Text.Jsonの非同期API

System.Text.Jsonは.NET Core 3.0以降で非同期のシリアライズ・デシリアライズAPIを提供しています。

これにより、大きなデータをファイルやネットワークストリームに対して効率的に処理できます。

非同期処理はUIの応答性を保ったり、I/O待ち時間を有効活用したい場合に有効です。

以下は、List<string>を非同期にファイルへシリアライズする例です。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
    static async Task Main()
    {
        var fruits = new List<string> { "りんご", "バナナ", "さくらんぼ" };
        // ファイルストリームを非同期で開く
        await using var stream = new FileStream("fruits.json", FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true);
        var options = new JsonSerializerOptions
        {
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All),
            WriteIndented = true
        };
        // 非同期でシリアライズしてストリームに書き込む
        await JsonSerializer.SerializeAsync(stream, fruits, options);
        Console.WriteLine("非同期シリアライズが完了しました。");
    }
}

このコードは、SerializeAsyncメソッドを使ってList<string>をJSON形式でファイルに非同期書き込みしています。

FileStreamuseAsync: true指定により、I/O操作も非同期で行われます。

ファイルに書き込まれたfruits.jsonの内容は以下のようになります。

[
  "りんご",
  "バナナ",
  "さくらんぼ"
]

非同期APIはメモリ使用量を抑えつつ、I/O待ち時間を効率的に処理できるため、大量データのシリアライズに適しています。

Newtonsoft.Jsonの非同期API

Newtonsoft.Jsonはバージョン12.0以降で非同期のシリアライズ・デシリアライズAPIをサポートしています。

JsonTextWriterStreamWriterを組み合わせて非同期にJSONを書き込むことが可能です。

以下は、List<string>を非同期にファイルへシリアライズする例です。

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
class Program
{
    static async Task Main()
    {
        var fruits = new List<string> { "りんご", "バナナ", "さくらんぼ" };
        await using var stream = new FileStream("fruits_newtonsoft.json", FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true);
        await using var streamWriter = new StreamWriter(stream);
        using var jsonWriter = new JsonTextWriter(streamWriter) { Formatting = Formatting.Indented };
        var serializer = new JsonSerializer();
        // 非同期でシリアライズしてストリームに書き込む
        await jsonWriter.WriteStartArrayAsync();
        foreach (var fruit in fruits)
        {
            await jsonWriter.WriteValueAsync(fruit);
        }
        await jsonWriter.WriteEndArrayAsync();
        await jsonWriter.FlushAsync();
        Console.WriteLine("Newtonsoft.Jsonの非同期シリアライズが完了しました。");
    }
}

この例では、JsonTextWriterの非同期メソッドを使ってJSON配列をストリームに書き込んでいます。

JsonSerializer.SerializeAsyncのような単一メソッドは存在しないため、手動で配列の開始・終了を書き込み、要素を一つずつ非同期で書いています。

ファイルfruits_newtonsoft.jsonの内容は以下の通りです。

[
  "りんご",
  "バナナ",
  "さくらんぼ"
]

Newtonsoft.Jsonの非同期APIは細かい制御が可能ですが、System.Text.Jsonに比べてやや冗長になる点に注意が必要です。

大量データのストリーム処理や部分的な書き込みに向いています。

JsonNode/JsonDocumentとの連携

部分更新に向いたDiff方式

System.Text.JsonJsonNodeJsonDocumentは、JSONデータを動的に操作できるAPIとして便利です。

特に、既存のJSON構造の一部だけを更新したい場合に役立ちます。

これにより、全体を再シリアライズすることなく、差分(Diff)を適用して効率的に部分更新が可能です。

以下は、JsonNodeを使ってJSONの一部を変更する例です。

using System;
using System.Text.Json;
using System.Text.Json.Nodes;
class Program
{
    static void Main()
    {
        string json = @"
        {
            ""Name"": ""山田太郎"",
            ""Age"": 30,
            ""Address"": {
                ""City"": ""東京"",
                ""Street"": ""千代田区1-1""
            }
        }";
        // JSON文字列をJsonNodeとして読み込む
        JsonNode rootNode = JsonNode.Parse(json);
        // 部分的に値を更新
        rootNode["Age"] = 31;
        rootNode["Address"]["Street"] = "千代田区2-2";
        // 更新後のJSONを文字列化
        string updatedJson = rootNode.ToJsonString(new JsonSerializerOptions
        {
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            WriteIndented = true
        });
        Console.WriteLine(updatedJson);
    }
}
{
  "Name": "山田太郎",
  "Age": 31,
  "Address": {
    "City": "東京",
    "Street": "千代田区2-2"
  }
}

このように、JsonNodeを使うと、JSONの特定のノードだけを直接操作でき、差分更新が簡単に行えます。

JsonDocumentは読み取り専用ですが、JsonNodeは書き込みも可能なため、部分更新に適しています。

Diff方式の利点は、巨大なJSONデータの一部だけを変更して再シリアライズのコストを抑えられることです。

APIや設定ファイルの一部だけを動的に書き換えたい場合などに有効です。

動的なフィルタリング処理

JsonNodeJsonDocumentは、動的にJSONの内容を解析・抽出できるため、条件に応じたフィルタリング処理にも適しています。

例えば、JSON配列の中から特定の条件を満たす要素だけを抽出したり、不要な要素を除外したりすることが可能です。

以下は、JsonNodeを使ってJSON配列から特定の条件に合う要素だけを抽出する例です。

using System;
using System.Text.Json;
using System.Text.Json.Nodes;

class Program
{
    static void Main()
    {
        // 元の JSON 文字列
        string json = @"
        [
            { ""Name"": ""山田太郎"", ""Age"": 30 },
            { ""Name"": ""鈴木花子"", ""Age"": 25 },
            { ""Name"": ""佐藤次郎"", ""Age"": 40 }
        ]";

        // JSON 配列を JsonNode としてパースし、JsonArray を取得
        JsonArray jsonArray = JsonNode.Parse(json).AsArray();

        // 年齢が 30 以上の要素だけを格納するための新しい JsonArray
        var filtered = new JsonArray();

        foreach (var item in jsonArray)
        {
            // 各要素の "Age" を取り出して比較
            int age = item["Age"].GetValue<int>();
            if (age >= 30)
            {
                // クローンを作成してから追加
                filtered.Add(item.DeepClone());
            }
        }

        // フィルタリング結果を見やすくインデント付きでシリアル化
        string filteredJson = filtered.ToJsonString(new JsonSerializerOptions
        {
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            WriteIndented = true
        });
        Console.WriteLine(filteredJson);
    }
}
[
  {
    "Name": "山田太郎",
    "Age": 30
  },
  {
    "Name": "佐藤次郎",
    "Age": 40
  }
]

このように、JsonNodeを使うと動的にJSONの内容を操作し、条件に応じたフィルタリングや加工が簡単に行えます。

Newtonsoft.JsonJObjectJArrayに似た使い勝手で、柔軟なJSON操作が可能です。

動的なフィルタリングは、APIレスポンスの一部だけを抽出したい場合や、設定ファイルの特定項目だけを取得・変更したい場合に役立ちます。

JSONの構造が固定されていないケースでも柔軟に対応できます。

まとめ

この記事では、C#でのList<T>をJSONに高速かつ柔軟に変換する方法を、System.Text.JsonNewtonsoft.Jsonの両ライブラリを中心に解説しました。

基本的なシリアライズ手順から、オプション設定による挙動調整、ネスト構造や辞書型の扱い、パフォーマンスのポイント、エラー対策、属性による制御、非同期処理、さらにJsonNodeJsonDocumentを活用した部分更新や動的フィルタリングまで幅広く理解できます。

用途や要件に応じて最適な手法を選択し、効率的なJSON処理を実現してください。

関連記事

Back to top button
目次へ