ファイル

【C#】System.Text.JsonとNewtonsoft.Jsonで学ぶJSONシリアライズの基本と活用法

JSONシリアライズはC#オブジェクトをテキスト化して保存や通信を簡単にする手段です。

標準のSystem.Text.Jsonは軽量で速く、Newtonsoft.Jsonは機能が豊富です。

用途が簡単なら前者、複雑なマッピングやカスタム変換が必要なら後者が適しています。

目次から探す
  1. JSONシリアライズの位置付け
  2. System.Text.Jsonの特徴
  3. System.Text.Jsonの基本操作
  4. System.Text.Jsonのカスタマイズ
  5. Newtonsoft.Jsonの特徴
  6. Newtonsoft.Jsonの基本操作
  7. Newtonsoft.Jsonのカスタマイズ
  8. 両ライブラリの比較
  9. シナリオ別サンプル
  10. エラーハンドリング
  11. セキュリティ考慮
  12. 性能最適化のヒント
  13. よくある落とし穴
  14. 関連ツールと拡張
  15. .NETバージョン別対応状況
  16. まとめ

JSONシリアライズの位置付け

JSONとは

JSON(JavaScript Object Notation)は、データをテキスト形式で表現するための軽量なフォーマットです。

主にWebアプリケーションでサーバーとクライアント間のデータ交換に使われていますが、近年ではさまざまなプログラミング言語やシステムで広く利用されています。

JSONは人間にも読みやすく、構造がシンプルであるため、データの保存や通信に適しています。

JSONの基本的な構造は、キーと値のペアで表されるオブジェクト(連想配列のようなもの)と、値のリストである配列の2つを中心に構成されています。

例えば、以下のような形式です。

{
  "Name": "田中太郎",
  "Age": 30,
  "Hobbies": ["読書", "サッカー", "プログラミング"]
}

この例では、名前や年齢、趣味のリストがJSON形式で表現されています。

JSONは文字列、数値、真偽値、配列、オブジェクト、nullの6つの基本データ型をサポートしており、これらを組み合わせて複雑なデータ構造を表現できます。

JSONは言語に依存しないため、C#をはじめとした多くのプログラミング言語で扱うことが可能です。

特にC#では、JSONを扱うための専用ライブラリが充実しており、シリアライズやデシリアライズを簡単に行えます。

シリアライズの目的

シリアライズとは、プログラム内のオブジェクトやデータ構造を、保存や通信に適した形式に変換する処理のことを指します。

JSONシリアライズの場合は、C#のオブジェクトをJSON形式の文字列に変換することを意味します。

逆に、JSON文字列からC#のオブジェクトに戻す処理はデシリアライズと呼ばれます。

シリアライズの主な目的は以下の通りです。

  • データの永続化

オブジェクトの状態をファイルやデータベースに保存し、後で再利用できるようにします。

JSON形式はテキストファイルとして保存できるため、可読性が高く、メンテナンスもしやすいです。

  • ネットワーク通信

クライアントとサーバー間でデータをやり取りする際に、オブジェクトをJSON文字列に変換して送信します。

JSONは軽量であり、HTTP通信などで効率的にデータを交換できます。

  • 異なるシステム間のデータ交換

異なるプログラミング言語やプラットフォーム間でデータをやり取りする場合、共通のフォーマットとしてJSONが使われます。

シリアライズにより、C#のオブジェクトを他の言語でも扱いやすい形式に変換できます。

  • 設定情報の管理

アプリケーションの設定やユーザーの環境情報をJSON形式で保存し、起動時に読み込むことで柔軟な設定管理が可能になります。

C#でJSONシリアライズを行うことで、これらの目的を簡単に実現できます。

特に、System.Text.JsonNewtonsoft.Jsonといったライブラリを使うと、複雑なオブジェクトも簡単にJSONに変換でき、またJSONからオブジェクトへの復元もスムーズに行えます。

シリアライズは単なるデータ変換にとどまらず、アプリケーションの設計やデータフローにおいて重要な役割を果たします。

適切なシリアライズ手法を選ぶことで、パフォーマンスや保守性の向上にもつながります。

System.Text.Jsonの特徴

軽量・高速アーキテクチャ

System.Text.Jsonは、.NET Core 3.0以降で利用可能なMicrosoft純正のJSONシリアライズライブラリであり、軽量かつ高速な処理を実現しています。

内部的には、低レベルのバイト操作やSpan<T>を活用しており、余計なメモリ割り当てを抑えながら高速にシリアライズとデシリアライズを行います。

例えば、従来の文字列操作に比べて、System.Text.JsonはUTF-8エンコーディングを前提としているため、バイト配列を直接操作できる点がパフォーマンス向上に寄与しています。

これにより、Web APIのレスポンス生成や大量データの処理においても高速な動作が期待できます。

また、JSONの読み書きに特化したUtf8JsonReaderUtf8JsonWriterといった構造体を提供しており、これらは低レベルのストリーム処理を効率的に行うためのAPIです。

これらを活用することで、カスタムなシリアライズ処理や部分的なJSON操作も高速に実装可能です。

省メモリ指向の設計

System.Text.Jsonは、メモリ使用量を最小限に抑える設計がなされています。

特に、文字列の中間生成を減らし、バッファの再利用やSpan<T>を多用することで、ガベージコレクションの負荷を軽減しています。

例えば、JSON文字列を読み込む際に、Utf8JsonReaderはバイト配列を直接参照しながら解析を行うため、不要な文字列コピーが発生しません。

これにより、大量のJSONデータを扱う場合でもメモリ消費を抑えつつ高速に処理できます。

また、シリアライズ時もUtf8JsonWriterを使うことで、バッファに直接書き込みを行い、途中で文字列を生成することなく効率的にJSONを構築します。

これらの設計は、特にサーバーサイドの高負荷環境やリソース制約のある環境で効果を発揮します。

標準ライブラリ統合による利便性

System.Text.Jsonは.NETの標準ライブラリに組み込まれているため、追加の外部依存が不要です。

これにより、プロジェクトの依存関係を増やさずにJSON処理を行えます。

また、ASP.NET CoreのWeb APIでは、デフォルトのJSONシリアライザとしてSystem.Text.Jsonが採用されており、コントローラーのモデルバインディングやレスポンス生成がシームレスに連携します。

これにより、設定をほとんど変更せずにJSONの入出力を行うことが可能です。

さらに、JsonSerializerOptionsを使って細かい挙動のカスタマイズもでき、プロパティ名の変換(キャメルケースやスネークケース)、インデントの有無、Null値の扱いなどを柔軟に設定できます。

これらのオプションは標準APIとして提供されているため、学習コストも低く、メンテナンス性が高い点も利点です。

まとめると、System.Text.Jsonは.NET環境に最適化された軽量かつ高速なJSON処理を提供し、標準ライブラリとしての統合により利便性と保守性を両立しています。

System.Text.Jsonの基本操作

オブジェクトをJSON文字列へ

最小コード例

C#のオブジェクトをJSON文字列に変換する最もシンプルな方法は、JsonSerializer.Serializeメソッドを使うことです。

以下のサンプルコードでは、PersonクラスのインスタンスをJSON文字列にシリアライズしています。

using System;
using System.Text.Json;
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { Name = "田中太郎", Age = 30 };
        // オブジェクトをJSON文字列に変換
        string jsonString = JsonSerializer.Serialize(person);
        Console.WriteLine(jsonString);
    }
}
{"Name":"田中太郎","Age":30}

このコードでは、JsonSerializer.Serializeにオブジェクトを渡すだけで、対応するJSON文字列が生成されます。

デフォルトでは、プロパティ名はクラスの名前のまま出力され、インデントは付与されません。

インデント付き出力

JSONを人間が読みやすい形で出力したい場合は、JsonSerializerOptionsWriteIndentedプロパティをtrueに設定します。

これにより、改行やスペースが挿入され、整形されたJSONが生成されます。

using System;
using System.Text.Json;
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { Name = "田中太郎", Age = 30 };
        var options = new JsonSerializerOptions
        {
            WriteIndented = true, // インデント付き出力を有効化
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping // エスケープを無効化
        };
        string jsonString = JsonSerializer.Serialize(person, options);
        Console.WriteLine(jsonString);
    }
}
{
  "Name": "田中太郎",
  "Age": 30
}

このように、WriteIndentedを使うことで、ログ出力や設定ファイルの保存時に見やすいJSONを生成できます。

JSON文字列をオブジェクトへ

ジェネリック型での復元

JSON文字列をC#のオブジェクトに変換するには、JsonSerializer.Deserialize<T>メソッドを使います。

ジェネリック型パラメータTに復元したい型を指定することで、安全にデシリアライズが可能です。

using System;
using System.Text.Json;
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
class Program
{
    static void Main()
    {
        string jsonString = "{\"Name\":\"田中太郎\",\"Age\":30}";
        // JSON文字列をPersonオブジェクトに変換
        Person person = JsonSerializer.Deserialize<Person>(jsonString);
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
    }
}
Name: 田中太郎, Age: 30

この方法は型安全であり、JSONの構造がPersonクラスに合致していれば正しく復元されます。

もしJSONに存在しないプロパティがあっても無視され、存在しないプロパティはデフォルト値になります。

非同期APIの利用

大きなJSONデータをファイルやネットワークから読み込む場合は、非同期APIを使うことでアプリケーションの応答性を保てます。

JsonSerializerはストリームを使った非同期のシリアライズ・デシリアライズをサポートしています。

以下は、非同期でJSON文字列をストリームから読み込み、オブジェクトに復元する例です。

using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
class Program
{
    static async Task Main()
    {
        string jsonString = "{\"Name\":\"田中太郎\",\"Age\":30}";
        // JSON文字列をMemoryStreamに書き込む(実際はファイルやネットワークストリーム)
        using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonString));
        // 非同期でストリームからデシリアライズ
        Person person = await JsonSerializer.DeserializeAsync<Person>(stream);
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
    }
}
Name: 田中太郎, Age: 30

このように、DeserializeAsync<T>を使うと、I/O操作に伴う待機時間を非同期で処理でき、UIのフリーズやスレッドのブロックを防げます。

シリアライズにもSerializeAsyncメソッドが用意されており、同様に非同期処理が可能です。

System.Text.Jsonのカスタマイズ

JsonSerializerOptions

JsonSerializerOptionsは、System.Text.Jsonのシリアライズやデシリアライズの挙動を細かく制御するための設定クラスです。

これを使うことで、命名規則の変更やインデントの有無、Null値の扱いなどを柔軟にカスタマイズできます。

命名規則の変更

デフォルトでは、C#のプロパティ名はそのままJSONのキーとして出力されますが、API仕様や他システムとの連携で命名規則を変えたい場合があります。

JsonSerializerOptionsPropertyNamingPolicyプロパティにJsonNamingPolicy.CamelCaseを設定すると、プロパティ名をキャメルケース(先頭小文字)に変換できます。

using System;
using System.Text.Json;
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { FirstName = "太郎", LastName = "田中" };
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = true,
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        };
        string jsonString = JsonSerializer.Serialize(person, options);
        Console.WriteLine(jsonString);
    }
}
{
  "firstName": "太郎",
  "lastName": "田中"
}

独自の命名規則を適用したい場合は、JsonNamingPolicyを継承してカスタムポリシーを作成し、PropertyNamingPolicyに設定することも可能です。

インデントと書式設定

JSONを読みやすく整形したい場合は、WriteIndentedプロパティをtrueに設定します。

これにより、改行やスペースが挿入されて見やすいJSONが生成されます。

var options = new JsonSerializerOptions
{
    WriteIndented = true
};

逆に、デフォルトのfalseにすると、コンパクトな1行のJSONが生成されます。

用途に応じて使い分けてください。

Null値の扱い

デフォルトでは、オブジェクトのプロパティがnullの場合でもJSONに含まれます。

これを除外したい場合は、DefaultIgnoreConditionプロパティにJsonIgnoreCondition.WhenWritingNullを設定します。

var options = new JsonSerializerOptions
{
    DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
};

この設定により、nullのプロパティはJSONに出力されなくなり、データ量を削減できます。

文字エンコーディング設定

System.Text.JsonはUTF-8を標準としていますが、JsonSerializerOptionsEncoderプロパティでエンコーディングの挙動を制御できます。

例えば、HTMLエスケープを無効化したい場合はJavaScriptEncoder.UnsafeRelaxedJsonEscapingを設定します。

using System.Text.Encodings.Web;
var options = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

これにより、<>などの文字がエスケープされずにそのまま出力されます。

HTMLコンテンツを含むJSONを扱う際に便利です。

カスタムコンバーター

System.Text.Jsonは標準で多くの型をサポートしていますが、独自の型や特殊な変換ルールが必要な場合はカスタムコンバーターを作成して登録できます。

シンプルな値型変換

例えば、DateTimeを特定のフォーマットでシリアライズしたい場合、JsonConverter<T>を継承してカスタムコンバーターを作成します。

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class DateTimeConverter : JsonConverter<DateTime>
{
    private const string Format = "yyyy-MM-dd";
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string dateString = reader.GetString();
        return DateTime.ParseExact(dateString, Format, null);
    }
    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString(Format));
    }
}

このコンバーターは、DateTimeyyyy-MM-dd形式の文字列としてシリアライズし、同じ形式でデシリアライズします。

属性での適用方法

作成したカスタムコンバーターは、対象のプロパティやクラスに[JsonConverter(typeof(ConverterType))]属性を付けて適用できます。

public class Event
{
    public string Name { get; set; }
    [JsonConverter(typeof(DateTimeConverter))]
    public DateTime Date { get; set; }
}

このように属性を使うと、特定のプロパティだけに変換ルールを限定できます。

オプション経由での登録

また、JsonSerializerOptionsConvertersコレクションにカスタムコンバーターを追加して、グローバルに適用することも可能です。

var options = new JsonSerializerOptions();
options.Converters.Add(new DateTimeConverter());
string json = JsonSerializer.Serialize(eventInstance, options);

この方法は、複数のクラスで同じ変換ルールを使いたい場合に便利です。

ストリーム入出力

System.Text.Jsonはストリームを直接扱うAPIを提供しており、大容量データの処理やパフォーマンス最適化に役立ちます。

Utf8JsonWriterでの高速書き込み

Utf8JsonWriterは、バッファに直接JSONを書き込むための低レベルAPIです。

これを使うと、文字列を一旦生成せずに高速にJSONを構築できます。

using System;
using System.Buffers;
using System.Text.Json;

class Program
{
    static void Main()
    {
        var buffer = new ArrayBufferWriter<byte>();
        using var writer = new Utf8JsonWriter(buffer, new JsonWriterOptions
        {
            Indented = true,
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        });

        writer.WriteStartObject();
        writer.WriteString("Name", "田中太郎");
        writer.WriteNumber("Age", 30);
        writer.WriteEndObject();
        writer.Flush();

        // バッファの内容を UTF-8 文字列として取得
        string json = System.Text.Encoding.UTF8.GetString(buffer.WrittenSpan);
        Console.WriteLine(json);
    }
}
{
  "Name": "田中太郎",
  "Age": 30
}

この方法は、カスタムなJSON構造を効率的に生成したい場合に適しています。

Utf8JsonReaderでの大容量読み取り

Utf8JsonReaderは、バイト配列やストリームからJSONを高速に読み取るための構造体です。

トークン単位でJSONを解析できるため、大きなJSONファイルを部分的に処理したり、必要なデータだけを抽出したりする用途に向いています。

using System;
using System.Text;
using System.Text.Json;
class Program
{
    static void Main()
    {
        string json = "{\"Name\":\"田中太郎\",\"Age\":30}";
        byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
        var reader = new Utf8JsonReader(jsonBytes);
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.PropertyName)
            {
                string propertyName = reader.GetString();
                reader.Read();
                switch (propertyName)
                {
                    case "Name":
                        string name = reader.GetString();
                        Console.WriteLine($"Name: {name}");
                        break;
                    case "Age":
                        int age = reader.GetInt32();
                        Console.WriteLine($"Age: {age}");
                        break;
                }
            }
        }
    }
}
Name: 田中太郎
Age: 30

このように、Utf8JsonReaderを使うと、JSONの構造を細かく制御しながら効率的に読み取れます。

大規模データのストリーム処理や部分的な解析に適しています。

Newtonsoft.Jsonの特徴

豊富な設定オプション

Newtonsoft.Json(通称Json.NET)は、非常に多彩な設定オプションを備えている点が大きな特徴です。

これにより、細かなシリアライズ・デシリアライズの挙動を自在にコントロールできます。

代表的な設定項目には以下のようなものがあります。

  • Formatting

JSONの出力形式を制御します。

Formatting.Indentedを指定すると、インデント付きの読みやすいJSONが生成されます。

デフォルトはコンパクトな1行出力です。

  • NullValueHandling

NullValueHandling.Ignoreを設定すると、nullのプロパティをJSONに含めないようにできます。

これにより、不要なデータを省略し、通信量やファイルサイズを削減可能です。

  • DefaultValueHandling

デフォルト値のプロパティをシリアライズするかどうかを制御します。

例えば、数値の0やboolのfalseなど、初期値のプロパティを省略できます。

  • ContractResolver

プロパティ名の変換やシリアライズ対象の制御を行うための機能です。

例えば、CamelCasePropertyNamesContractResolverを使うと、プロパティ名をキャメルケースに変換して出力できます。

  • ReferenceLoopHandling

循環参照があるオブジェクトをシリアライズする際の挙動を制御します。

IgnoreErrorSerializeなどのオプションがあり、無限ループを防止できます。

  • TypeNameHandling

オブジェクトの型情報をJSONに埋め込むかどうかを設定します。

多態性のあるオブジェクトを正しく復元する際に便利ですが、セキュリティリスクもあるため注意が必要です。

これらの設定は、JsonSerializerSettingsクラスでまとめて指定し、JsonConvert.SerializeObjectJsonConvert.DeserializeObjectの引数として渡します。

細かい制御が必要なシナリオにおいて、非常に強力なツールとなります。

柔軟なデータ変換

Newtonsoft.Jsonは、標準で多くの型をサポートするだけでなく、カスタムの変換ロジックを簡単に実装できる点が優れています。

JsonConverterクラスを継承して独自のシリアライズ・デシリアライズ処理を定義でき、複雑なデータ構造や特殊なフォーマットにも対応可能です。

例えば、日付のフォーマットを独自に指定したり、列挙型を文字列として扱ったり、特定のプロパティを条件付きでシリアライズしたりといった柔軟な制御が行えます。

また、JsonConverterは属性で特定のクラスやプロパティに適用できるほか、JsonSerializerSettingsConvertersコレクションに登録してグローバルに適用することも可能です。

さらに、Newtonsoft.Jsonは多態性のあるオブジェクトのシリアライズに強く、TypeNameHandlingを活用することで、派生クラスの情報を保持したままJSONに変換し、正確に復元できます。

LINQ to JSONの提供

Newtonsoft.Jsonは、JSONデータを動的に操作できるLINQ to JSON機能を提供しています。

JObjectJArrayといったクラスを使うことで、JSONをオブジェクトとして読み込み、LINQクエリやメソッドで柔軟にアクセス・編集できます。

例えば、JSONの一部だけを抽出したり、特定の条件に合う要素を検索したり、動的にプロパティを追加・削除したりすることが簡単に行えます。

using Newtonsoft.Json.Linq;
using System;
class Program
{
    static void Main()
    {
        string json = @"{
            'Name': '田中太郎',
            'Age': 30,
            'Hobbies': ['読書', 'サッカー', 'プログラミング']
        }";
        JObject obj = JObject.Parse(json);
        // プロパティの取得
        string name = (string)obj["Name"];
        Console.WriteLine($"Name: {name}");
        // 配列の操作
        JArray hobbies = (JArray)obj["Hobbies"];
        hobbies.Add("映画鑑賞");
        Console.WriteLine(obj.ToString());
    }
}

このように、Newtonsoft.JsonのLINQ to JSONは、静的なクラス定義に縛られずにJSONを扱いたい場合に非常に便利です。

動的なJSON構造の解析や編集、部分的な更新など、多様なシナリオで活用できます。

Newtonsoft.Jsonの基本操作

SerializeObjectによる変換

オプション省略時の書き方

Newtonsoft.JsonでC#のオブジェクトをJSON文字列に変換する基本的な方法は、JsonConvert.SerializeObjectメソッドを使うことです。

オプションを省略した場合、デフォルト設定でシリアライズが行われます。

以下のサンプルコードでは、PersonクラスのインスタンスをJSON文字列に変換しています。

using System;
using Newtonsoft.Json;
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { Name = "田中太郎", Age = 30 };
        // オプション省略でシリアライズ
        string jsonString = JsonConvert.SerializeObject(person);
        Console.WriteLine(jsonString);
    }
}
{"Name":"田中太郎","Age":30}

このように、SerializeObjectにオブジェクトを渡すだけで、簡単にJSON文字列が生成されます。

デフォルトではインデントは付かず、コンパクトな1行のJSONが出力されます。

Formatting.Indented使用例

JSONを人間が読みやすい形で出力したい場合は、Formatting.Indentedを指定します。

これにより、改行やスペースが挿入されて整形されたJSONが生成されます。

using System;
using Newtonsoft.Json;
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { Name = "田中太郎", Age = 30 };
        // インデント付きでシリアライズ
        string jsonString = JsonConvert.SerializeObject(person, Formatting.Indented);
        Console.WriteLine(jsonString);
    }
}
{
  "Name": "田中太郎",
  "Age": 30
}

このように、Formatting.Indentedを使うと、ログ出力や設定ファイルの保存時に見やすいJSONを生成できます。

DeserializeObjectによる復元

型指定の安全な変換

JSON文字列をC#のオブジェクトに変換するには、JsonConvert.DeserializeObject<T>メソッドを使います。

ジェネリック型パラメータTに復元したい型を指定することで、型安全にデシリアライズが可能です。

using System;
using Newtonsoft.Json;
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
class Program
{
    static void Main()
    {
        string jsonString = "{\"Name\":\"田中太郎\",\"Age\":30}";
        // JSON文字列をPersonオブジェクトに変換
        Person person = JsonConvert.DeserializeObject<Person>(jsonString);
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
    }
}
Name: 田中太郎, Age: 30

この方法は、JSONの構造が指定したクラスに合致していれば正しく復元されます。

型が異なる場合は例外が発生するため、エラーハンドリングが必要です。

dynamic型の活用

Newtonsoft.Jsonは、dynamic型を使ってJSONを動的に扱うこともできます。

型を事前に定義しなくても、JSONの構造に応じてプロパティにアクセスできるため、柔軟な処理が可能です。

using System;
using Newtonsoft.Json;
class Program
{
    static void Main()
    {
        string jsonString = "{\"Name\":\"田中太郎\",\"Age\":30}";
        // dynamic型でデシリアライズ
        dynamic obj = JsonConvert.DeserializeObject<dynamic>(jsonString);
        Console.WriteLine($"Name: {obj.Name}, Age: {obj.Age}");
    }
}
Name: 田中太郎, Age: 30

ただし、dynamicはコンパイル時の型チェックが行われないため、実行時にプロパティが存在しない場合や型が異なる場合に例外が発生するリスクがあります。

用途に応じて使い分けることが重要です。

Newtonsoft.Jsonのカスタマイズ

JsonSerializerSettings

JsonSerializerSettingsは、Newtonsoft.Jsonのシリアライズ・デシリアライズの挙動を細かく制御するための設定クラスです。

多彩なオプションを組み合わせて、柔軟なJSON処理が可能になります。

ContractResolverでの命名規則

ContractResolverは、シリアライズ時のプロパティ名の変換やシリアライズ対象の制御を行う機能です。

代表的な実装にCamelCasePropertyNamesContractResolverがあり、これを設定するとC#のプロパティ名をキャメルケースに変換してJSONに出力します。

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
class Program
{
    static void Main()
    {
        var person = new Person { FirstName = "太郎", LastName = "田中" };
        var settings = new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            Formatting = Formatting.Indented
        };
        string json = JsonConvert.SerializeObject(person, settings);
        Console.WriteLine(json);
    }
}
{
  "firstName": "太郎",
  "lastName": "田中"
}

独自の命名規則を適用したい場合は、DefaultContractResolverを継承してカスタム実装を作成することも可能です。

NullValueHandlingとDefaultValueHandling

NullValueHandlingは、nullのプロパティをJSONに含めるかどうかを制御します。

NullValueHandling.Ignoreを設定すると、nullのプロパティはシリアライズされません。

DefaultValueHandlingは、プロパティのデフォルト値(例えば数値の0やboolのfalse)をシリアライズするかどうかを制御します。

DefaultValueHandling.Ignoreを指定すると、デフォルト値のプロパティは省略されます。

var settings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    DefaultValueHandling = DefaultValueHandling.Ignore,
    Formatting = Formatting.Indented
};

これらを組み合わせることで、不要なデータを省き、JSONのサイズを削減できます。

ReferenceLoopHandlingの設定

オブジェクトの循環参照がある場合、シリアライズ時に無限ループが発生する恐れがあります。

ReferenceLoopHandlingでその挙動を制御できます。

  • ReferenceLoopHandling.Error(デフォルト): 循環参照があると例外をスローします
  • ReferenceLoopHandling.Ignore: 循環参照のプロパティを無視してシリアライズします
  • ReferenceLoopHandling.Serialize: 循環参照を許容してシリアライズしますが、JSONの整合性に注意が必要です
var settings = new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};

循環参照を含む複雑なオブジェクトを扱う際に重要な設定です。

TypeNameHandlingの注意点

TypeNameHandlingは、オブジェクトの型情報をJSONに埋め込むための設定です。

多態性のあるオブジェクトを正しく復元する際に便利ですが、セキュリティリスクが伴います。

  • TypeNameHandling.AllTypeNameHandling.Autoを使うと、JSONに$typeプロパティが追加され、復元時に型を特定できます
  • しかし、悪意のあるJSONを受け取ると、任意の型のインスタンス生成につながる恐れがあり、リモートコード実行の脆弱性を招く可能性があります

安全に使うためには、信頼できるデータのみで使用し、SerializationBinderで許可する型を制限することが推奨されます。

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Auto,
    SerializationBinder = new KnownTypesBinder() // 独自実装で許可型を制限
};

カスタムJsonConverter

JsonConverterを継承して独自のシリアライズ・デシリアライズ処理を実装できます。

これにより、標準では対応できない特殊な型やフォーマットを扱えます。

簡易例の実装

以下は、DateTimeyyyy-MM-dd形式の文字列でシリアライズ・デシリアライズする簡単なカスタムコンバーターの例です。

using System;
using Newtonsoft.Json;
public class DateOnlyConverter : JsonConverter<DateTime>
{
    private const string Format = "yyyy-MM-dd";
    public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString(Format));
    }
    public override DateTime ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        string dateStr = (string)reader.Value;
        return DateTime.ParseExact(dateStr, Format, null);
    }
}

このコンバーターをクラスやプロパティに属性で適用できます。

public class Event
{
    public string Name { get; set; }
    [JsonConverter(typeof(DateOnlyConverter))]
    public DateTime Date { get; set; }
}

リーダー/ライターの詳細制御

JsonReaderJsonWriterを使うことで、JSONの読み書きを細かく制御できます。

例えば、複雑なネスト構造や特殊なフォーマットの処理に対応可能です。

public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
{
    writer.WriteStartObject();
    writer.WritePropertyName("date");
    writer.WriteValue(value.ToString("yyyy-MM-dd"));
    writer.WritePropertyName("timestamp");
    writer.WriteValue(value.Ticks);
    writer.WriteEndObject();
}

このように、単純な文字列変換だけでなく、複数のプロパティを持つJSONオブジェクトとして出力することもできます。

JTokenとLINQ to JSON

Newtonsoft.Jsonは、JSONを動的に操作できるJTokenを基盤としたLINQ to JSON機能を提供しています。

これにより、静的なクラス定義なしにJSONの解析や編集が可能です。

JObject・JArrayの生成

JObjectはJSONオブジェクトを表し、JArrayはJSON配列を表します。

これらを使って動的にJSONを生成・編集できます。

using Newtonsoft.Json.Linq;
using System;
class Program
{
    static void Main()
    {
        JObject obj = new JObject
        {
            ["Name"] = "田中太郎",
            ["Age"] = 30,
            ["Hobbies"] = new JArray("読書", "サッカー")
        };
        Console.WriteLine(obj.ToString());
    }
}
{
  "Name": "田中太郎",
  "Age": 30,
  "Hobbies": [
    "読書",
    "サッカー"
  ]
}

クエリと操作例

LINQを使ってJObjectJArrayの要素を検索・操作できます。

例えば、配列内の特定の要素を取得したり、プロパティを追加・削除したりできます。

JArray hobbies = (JArray)obj["Hobbies"];
// 要素の追加
hobbies.Add("プログラミング");
// 条件に合う要素の検索
var soccer = hobbies.FirstOrDefault(h => h.ToString() == "サッカー");
Console.WriteLine($"Found hobby: {soccer}");
// プロパティの削除
obj.Remove("Age");
Console.WriteLine(obj.ToString());
Found hobby: サッカー
{
  "Name": "田中太郎",
  "Hobbies": [
    "読書",
    "サッカー",
    "プログラミング"
  ]
}

このように、JTokenを活用すると、動的かつ柔軟にJSONデータを操作でき、静的な型に縛られない処理が可能です。

両ライブラリの比較

パフォーマンス計測ポイント

System.Text.JsonNewtonsoft.Jsonのパフォーマンスを比較する際には、主に以下のポイントに注目します。

  • シリアライズ速度

オブジェクトをJSON文字列に変換する速度は、APIレスポンスの生成やファイル書き出しの効率に直結します。

System.Text.Jsonは低レベルのバイト操作を活用しており、一般的に高速です。

一方、Newtonsoft.Jsonは多機能ゆえに若干のオーバーヘッドがあります。

  • デシリアライズ速度

JSON文字列からオブジェクトに復元する速度も重要です。

特に大量データや高頻度の処理では、System.Text.Jsonの高速なパース性能が有利です。

  • メモリ使用量

メモリ効率はサーバーのスケーラビリティに影響します。

System.Text.JsonSpan<T>やバッファの再利用を多用し、メモリ割り当てを抑制しています。

Newtonsoft.Jsonは柔軟性の代償としてメモリ消費がやや多くなる傾向があります。

  • 非同期処理の対応

両者とも非同期APIを提供していますが、System.Text.Jsonは.NET標準の非同期ストリーム処理と密接に連携しており、I/Oバウンド処理での効率が高いです。

パフォーマンス計測は、実際のアプリケーションのデータ構造や使用シナリオに依存するため、ベンチマークを行う際は対象のデータセットや処理内容に合わせて計測することが重要です。

機能対コストのバランス

Newtonsoft.Jsonは非常に豊富な機能を持ち、複雑なシリアライズ要件や特殊なデータ構造に対応可能です。

カスタムコンバーターの柔軟性、多態性のサポート、LINQ to JSONなど、多彩な機能が揃っています。

一方で、その多機能さがパフォーマンスやメモリ使用量の増加につながることがあります。

特に軽量で高速な処理が求められる環境では、オーバーヘッドが気になる場合があります。

System.Text.Jsonは機能を絞り込み、パフォーマンスと軽量性を優先しています。

標準的なシリアライズ・デシリアライズには十分な機能を備えていますが、Newtonsoft.Jsonに比べると一部の高度な機能や柔軟性は制限されています。

したがって、機能の豊富さとパフォーマンスのバランスを考慮し、プロジェクトの要件に応じて適切なライブラリを選択することが重要です。

.NETバージョンへの適合性

System.Text.Jsonは.NET Core 3.0以降および.NET 5/6/7などの最新の.NET環境で標準搭載されています。

追加のパッケージを導入せずに利用できるため、最新の.NET環境での開発に最適です。

一方、Newtonsoft.Jsonは.NET Framework時代から広く使われており、古い.NET Framework環境でも利用可能です。

レガシーなプロジェクトや.NET Framework 4.x系での互換性が必要な場合は、Newtonsoft.Jsonが選択肢となります。

また、Newtonsoft.Jsonは.NET Standardに対応しているため、クロスプラットフォーム開発でも利用しやすいです。

既存コードからの移行要素

既存のプロジェクトでNewtonsoft.Jsonを使っている場合、System.Text.Jsonへの移行にはいくつかの注意点があります。

  • APIの違い

メソッド名やオプションの指定方法が異なるため、コードの書き換えが必要です。

特にカスタムコンバーターや細かい設定を使っている場合は、対応する機能の差異を確認する必要があります。

  • 機能の制限

System.Text.Jsonは一部の高度な機能(例:多態性のサポート、複雑なカスタムコンバーター、LINQ to JSONのような動的操作)が制限されています。

これらを多用している場合は移行が難しいことがあります。

  • 動作の差異

デフォルトの挙動(例:プロパティ名の大文字小文字の扱い、Null値の扱い、日付フォーマットなど)が異なるため、動作検証が必須です。

  • 依存関係の整理

Newtonsoft.Jsonのパッケージを削除し、System.Text.Jsonに切り替える際は、依存している他のライブラリやフレームワークの対応状況も確認してください。

移行は段階的に行い、テストを十分に実施した上で進めることが推奨されます。

場合によっては、両方のライブラリを併用するケースもあります。

シナリオ別サンプル

Web APIでのシリアライズ

ASP.NET Coreの自動バインド

ASP.NET Coreでは、コントローラーのアクションメソッドでJSONのシリアライズ・デシリアライズが自動的に行われます。

例えば、HTTPリクエストのボディに含まれるJSONをC#のオブジェクトにバインドし、レスポンスとしてオブジェクトをJSONで返すことが簡単にできます。

以下は、Personクラスを受け取り、同じオブジェクトを返すシンプルなAPIの例です。

using Microsoft.AspNetCore.Mvc;
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
[ApiController]
[Route("api/[controller]")]
public class PeopleController : ControllerBase
{
    [HttpPost]
    public ActionResult<Person> PostPerson([FromBody] Person person)
    {
        // 受け取ったPersonオブジェクトをそのまま返す
        return Ok(person);
    }
}

この例では、クライアントから送信されたJSONが自動的にPersonオブジェクトに変換され、戻り値のPersonもJSONにシリアライズされて返されます。

ASP.NET CoreのデフォルトではSystem.Text.Jsonが使用されます。

フォーマッター切り替え手順

ASP.NET CoreのJSONシリアライズは、デフォルトでSystem.Text.Jsonが使われていますが、Newtonsoft.Jsonに切り替えることも可能です。

既存のNewtonsoft.Jsonの機能を活用したい場合や互換性のために切り替えが必要な場合に有効です。

切り替え手順は以下の通りです。

  1. NuGetでMicrosoft.AspNetCore.Mvc.NewtonsoftJsonパッケージをインストールします。
  2. Startup.cs(またはProgram.cs)のConfigureServicesメソッドで、AddControllersAddNewtonsoftJsonを追加します。
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
            .AddNewtonsoftJson(options =>
            {
                options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
                // 必要に応じて設定を追加
            });
}

これにより、ASP.NET CoreのJSONシリアライズ・デシリアライズがNewtonsoft.Jsonに切り替わります。

ファイル保存と読み込み

設定ファイルへの応用

JSONは設定ファイルの保存形式としてもよく使われます。

C#のオブジェクトをJSONファイルにシリアライズして保存し、起動時に読み込んで復元することで、柔軟な設定管理が可能です。

以下は、AppSettingsクラスのインスタンスをJSONファイルに保存し、読み込む例です。

using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
public class AppSettings
{
    public string ApplicationName { get; set; }
    public int MaxUsers { get; set; }
}
class Program
{
    static async Task Main()
    {
        var settings = new AppSettings
        {
            ApplicationName = "MyApp",
            MaxUsers = 100
        };
        string filePath = "appsettings.json";
        // JSONファイルに保存(非同期)
        using (FileStream createStream = File.Create(filePath))
        {
            await JsonSerializer.SerializeAsync(createStream, settings, new JsonSerializerOptions { WriteIndented = true });
        }
        // JSONファイルから読み込み(非同期)
        using (FileStream openStream = File.OpenRead(filePath))
        {
            AppSettings loadedSettings = await JsonSerializer.DeserializeAsync<AppSettings>(openStream);
            Console.WriteLine($"ApplicationName: {loadedSettings.ApplicationName}, MaxUsers: {loadedSettings.MaxUsers}");
        }
    }
}
ApplicationName: MyApp, MaxUsers: 100

このように、JSONファイルを使った設定管理はシンプルで拡張性も高いです。

キャッシュ・メッセージキュー連携

Redis利用例

Redisは高速なインメモリデータストアとしてキャッシュやメッセージキューに使われます。

C#でRedisにオブジェクトを保存する際、JSONシリアライズを活用して文字列形式でデータを格納することが一般的です。

以下は、StackExchange.Redisライブラリを使い、PersonオブジェクトをJSONにシリアライズしてRedisに保存・取得する例です。

using System;
using System.Text.Json;
using StackExchange.Redis;
using System.Threading.Tasks;
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
class Program
{
    static async Task Main()
    {
        var redis = await ConnectionMultiplexer.ConnectAsync("localhost");
        IDatabase db = redis.GetDatabase();
        var person = new Person { Name = "田中太郎", Age = 30 };
        // シリアライズしてRedisに保存
        string json = JsonSerializer.Serialize(person);
        await db.StringSetAsync("person:1", json);
        // Redisから取得してデシリアライズ
        string cachedJson = await db.StringGetAsync("person:1");
        Person cachedPerson = JsonSerializer.Deserialize<Person>(cachedJson);
        Console.WriteLine($"Name: {cachedPerson.Name}, Age: {cachedPerson.Age}");
    }
}
Name: 田中太郎, Age: 30

この方法で、Redisの高速な読み書きを活かしつつ、複雑なオブジェクトを簡単に扱えます。

Blazor WebAssemblyクライアント

Blazor WebAssemblyはブラウザ上で動作するC#アプリケーションで、JSONシリアライズはAPI通信やローカルストレージの操作に欠かせません。

System.Text.Jsonが標準で利用されており、軽量かつ高速に動作します。

以下は、HTTPクライアントでJSONを取得し、オブジェクトにデシリアライズする例です。

@inject HttpClient Http
@code {
    private Person person;
    protected override async Task OnInitializedAsync()
    {
        string json = await Http.GetStringAsync("https://example.com/api/person/1");
        person = JsonSerializer.Deserialize<Person>(json);
    }
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

また、ローカルストレージにJSON文字列を保存・読み込みする際も、System.Text.Jsonでシリアライズ・デシリアライズを行うことで、型安全かつ効率的にデータ管理が可能です。

Blazor WebAssemblyの環境では、軽量でネイティブに統合されたSystem.Text.Jsonが特に適しています。

エラーハンドリング

不正JSONの検出方法

JSONのシリアライズやデシリアライズを行う際、不正なJSON文字列が入力されることがあります。

これを検出し適切に対処することは、アプリケーションの安定性を保つために重要です。

System.Text.Jsonでは、JsonSerializer.DeserializeUtf8JsonReaderを使った読み取り時に不正なJSONがあると、JsonExceptionがスローされます。

これをキャッチしてエラー処理を行います。

using System;
using System.Text.Json;
class Program
{
    static void Main()
    {
        string invalidJson = "{\"Name\":\"田中太郎\", \"Age\":30,,}"; // 不正なJSON
        try
        {
            var person = JsonSerializer.Deserialize<Person>(invalidJson);
        }
        catch (JsonException ex)
        {
            Console.WriteLine($"JSONの解析エラー: {ex.Message}");
        }
    }
}
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
JSONの解析エラー: '}' の直前に不正な文字があります。

同様に、Newtonsoft.JsonでもJsonConvert.DeserializeObjectで不正なJSONを読み込むとJsonReaderExceptionが発生します。

例外をキャッチしてログ出力やユーザー通知を行うことが推奨されます。

型不一致時の対策

JSONのデータ型とC#のプロパティ型が一致しない場合、デシリアライズ時に例外が発生したり、意図しない動作になることがあります。

これを防ぐための対策を紹介します。

  • 柔軟な型指定

プロパティの型をstringobjectdynamicにすることで、型の違いによる例外を回避できます。

ただし、型安全性は低下します。

  • カスタムコンバーターの利用

型変換のルールをカスタムコンバーターで定義し、異なる型のデータを適切に変換します。

例えば、数値が文字列として送られてくる場合に対応可能です。

  • JsonSerializerOptionsJsonSerializerSettingsの設定

例えば、System.Text.JsonJsonSerializerOptionsNumberHandlingを設定し、数値と文字列の相互変換を許可できます。

var options = new JsonSerializerOptions
{
    NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString
};
  • 事前バリデーション

JSONを受け取る前にスキーマ検証やJSON Schemaを使ったバリデーションを行い、型の不一致を早期に検出します。

循環参照の回避策

オブジェクトのプロパティが相互に参照し合う循環参照がある場合、シリアライズ時に無限ループが発生し、スタックオーバーフローや例外の原因となります。

これを回避する方法を説明します。

  • Newtonsoft.JsonReferenceLoopHandling設定

JsonSerializerSettingsReferenceLoopHandlingIgnoreに設定すると、循環参照のプロパティを無視してシリアライズします。

var settings = new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
string json = JsonConvert.SerializeObject(obj, settings);
  • System.Text.JsonReferenceHandler設定

.NET 5以降のSystem.Text.Jsonでは、ReferenceHandler.Preserveを使うことで循環参照をサポートし、参照のIDを付与して無限ループを防ぎます。

var options = new JsonSerializerOptions
{
    ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve,
    WriteIndented = true
};
string json = JsonSerializer.Serialize(obj, options);
  • 設計上の回避

循環参照が発生しないように、DTO(データ転送オブジェクト)を用いてシリアライズ対象のオブジェクトを分離したり、必要なプロパティだけを持つクラスを用意する方法もあります。

  • 属性による制御

Newtonsoft.Jsonでは[JsonIgnore]属性、System.Text.Jsonでは[JsonIgnore]属性を使い、循環参照を引き起こすプロパティをシリアライズ対象から除外できます。

これらの方法を組み合わせて、循環参照による問題を効果的に回避してください。

セキュリティ考慮

型名埋め込みリスク

JSONシリアライズ時にオブジェクトの型情報をJSONに埋め込む機能は、多態性のあるオブジェクトの正確な復元に便利ですが、セキュリティリスクを伴います。

特にNewtonsoft.JsonTypeNameHandling設定で型名を埋め込む場合、悪意のあるJSONにより任意の型のインスタンス生成が可能となり、リモートコード実行(RCE)などの深刻な脆弱性を引き起こす恐れがあります。

例えば、TypeNameHandling.Allを有効にした状態で信頼できないJSONをデシリアライズすると、攻撃者が悪意のある型を指定してシステム内部のコードを実行させることが可能です。

対策としては以下が挙げられます。

  • 信頼できるデータのみをデシリアライズする

外部からの入力を直接デシリアライズしないことが基本です。

  • SerializationBinderISerializationBinderで許可する型を制限する

Newtonsoft.Jsonでは、SerializationBinderを実装して安全な型のみを許可し、それ以外は拒否する設定が可能です。

  • TypeNameHandlingの使用を最小限に抑える

可能な限りTypeNameHandlingを無効にし、型情報の埋め込みを避けることが推奨されます。

  • System.Text.Jsonの利用

System.Text.Jsonはデフォルトで型名埋め込みをサポートしておらず、このリスクが低減されます。

エスケープ不足によるXSS

JSONデータをWebページに埋め込む際、適切なエスケープ処理が行われていないとクロスサイトスクリプティング(XSS)攻撃のリスクが高まります。

特に、JSON文字列内に<>&"などの特殊文字が含まれる場合、ブラウザがスクリプトとして解釈してしまう可能性があります。

System.Text.Jsonでは、デフォルトでHTMLエスケープが有効になっており、JavaScriptEncoderが特殊文字を適切にエスケープします。

一方、Newtonsoft.JsonはデフォルトでHTMLエスケープを行わないため、Webページに直接埋め込む場合は注意が必要です。

対策例:

  • HTMLエスケープを有効にする

System.Text.Encodings.Web.JavaScriptEncoderを利用してエスケープ処理を行います。

  • Newtonsoft.Jsonでのエスケープ

StringEscapeHandling.EscapeHtmlJsonSerializerSettingsStringEscapeHandlingに設定します。

var settings = new JsonSerializerSettings
{
    StringEscapeHandling = StringEscapeHandling.EscapeHtml
};
  • JSONを直接HTMLに埋め込まない

JSONはJavaScriptの変数として安全に扱うか、API経由で取得して動的に処理する方法が望ましいです。

デシリアライズ攻撃の防御

デシリアライズ攻撃は、悪意のあるJSONデータを利用してアプリケーションの挙動を不正に操作する攻撃手法です。

代表的な攻撃には、前述の型名埋め込みを悪用したリモートコード実行や、オブジェクトの状態を不正に変更するものがあります。

防御策は以下の通りです。

  • 信頼できるデータのみをデシリアライズする

外部からの入力は必ず検証・サニタイズし、信頼できないデータは処理しない。

  • 型の制限

Newtonsoft.JsonSerializationBinderSystem.Text.Jsonのカスタムコンバーターで、許可された型のみを復元するよう制限します。

  • 最小限の権限で実行

アプリケーションの実行環境を最小権限に設定し、万が一攻撃が成功しても被害を抑えます。

  • 最新のライブラリを使用する

セキュリティパッチが適用された最新版のJSONライブラリを利用します。

  • デシリアライズ時の例外処理

不正なデータを検出したら例外をキャッチし、適切に処理します。

これらの対策を組み合わせて、JSONのシリアライズ・デシリアライズに伴うセキュリティリスクを低減してください。

性能最適化のヒント

オブジェクトプール活用

JSONのシリアライズやデシリアライズ処理では、多数の一時的なオブジェクトが生成されることが多く、これがGC(ガベージコレクション)の負荷増大やパフォーマンス低下の原因となります。

これを軽減するために、オブジェクトプールを活用する方法があります。

オブジェクトプールとは、使い回し可能なオブジェクトのプール(再利用可能なストック)を用意し、新規生成のコストを削減する仕組みです。

特にUtf8JsonWriterUtf8JsonReaderのような低レベルAPIを使う場合、バッファやライターのインスタンスをプールして再利用すると効率的です。

.NET標準のSystem.Buffers.ArrayPool<T>を利用すると、バイト配列の割り当てと解放を効率化できます。

例えば、Utf8JsonWriterのバッファとしてArrayPool<byte>.Sharedを使うことで、メモリ割り当てを抑えつつ高速な書き込みが可能です。

using System;
using System.Buffers;
using System.Text;
using System.Text.Json;
class Program
{
    static void Main()
    {
        var buffer = ArrayPool<byte>.Shared.Rent(1024);
        try
        {
            var writer = new Utf8JsonWriter(buffer.AsSpan(0, 1024));
            writer.WriteStartObject();
            writer.WriteString("Name", "田中太郎");
            writer.WriteNumber("Age", 30);
            writer.WriteEndObject();
            writer.Flush();
            string json = Encoding.UTF8.GetString(buffer, 0, (int)writer.BytesCommitted);
            Console.WriteLine(json);
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(buffer);
        }
    }
}

このように、オブジェクトプールを活用することで、メモリ割り当ての頻度を減らし、GCの負荷を軽減してパフォーマンスを向上させられます。

非同期APIでのスループット向上

大量のJSONデータを扱う場合やI/O待ちが発生する処理では、非同期APIを活用することでスループットを向上させられます。

System.Text.JsonNewtonsoft.Jsonは、ストリームを使った非同期のシリアライズ・デシリアライズAPIを提供しています。

非同期処理を使うと、ファイルやネットワークからの読み書き中にスレッドをブロックせず、他の処理を並行して実行できます。

これにより、サーバーの応答性が向上し、全体の処理効率が改善します。

例えば、System.Text.JsonDeserializeAsync<T>SerializeAsyncを使うと、FileStreamNetworkStreamと組み合わせて効率的にJSON処理が可能です。

using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
    static async Task Main()
    {
        var person = new Person { Name = "田中太郎", Age = 30 };
        string filePath = "person.json";
        // 非同期でファイルにシリアライズ
        using (FileStream fs = File.Create(filePath))
        {
            await JsonSerializer.SerializeAsync(fs, person, new JsonSerializerOptions { WriteIndented = true });
        }
        // 非同期でファイルからデシリアライズ
        using (FileStream fs = File.OpenRead(filePath))
        {
            Person loaded = await JsonSerializer.DeserializeAsync<Person>(fs);
            Console.WriteLine($"Name: {loaded.Name}, Age: {loaded.Age}");
        }
    }
}
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

非同期APIを適切に使うことで、I/O待ち時間を有効活用し、アプリケーションのスループットを向上させられます。

バッファサイズ調整指針

JSONのシリアライズ・デシリアライズ時に使用するバッファサイズは、パフォーマンスに大きく影響します。

バッファが小さすぎると頻繁に拡張やI/Oが発生し、逆に大きすぎるとメモリ消費が増加します。

適切なバッファサイズは、処理するJSONデータの平均サイズやシステムのメモリ状況に応じて調整する必要があります。

一般的には数キロバイトから数十キロバイト程度が多く使われます。

System.Text.JsonUtf8JsonWriterUtf8JsonReaderでは、バッファを自前で用意して渡すことができるため、バッファサイズを明示的に制御可能です。

ArrayPool<byte>と組み合わせてバッファを再利用しつつ、適切なサイズを選ぶと効率的です。

また、JsonSerializerOptionsDefaultBufferSize(.NET 7以降)などの設定項目でバッファサイズを調整できる場合もあります。

バッファサイズ調整のポイントは以下の通りです。

  • 小さすぎるバッファは避ける

頻繁なバッファ拡張やI/Oが発生し、パフォーマンス低下の原因となります。

  • 大きすぎるバッファはメモリ浪費になる

特に多数の同時処理がある場合は注意が必要でしょう。

  • 実際のデータサイズに合わせて調整する

処理対象のJSONの平均サイズや最大サイズを考慮し、適切なバッファサイズを選択します。

  • プロファイリングで効果を検証する

実際のアプリケーションでパフォーマンス計測を行い、最適なバッファサイズを見極める。

これらを踏まえ、バッファサイズを適切に設定することで、JSON処理の効率を最大化できます。

よくある落とし穴

DateTimeフォーマット差異

JSONシリアライズにおけるDateTimeの扱いは、ライブラリ間で挙動が異なるため注意が必要です。

System.Text.JsonNewtonsoft.Jsonではデフォルトのフォーマットやタイムゾーンの扱いに差異があります。

  • System.Text.Jsonのデフォルト

ISO 8601形式の文字列(例: "2024-06-01T12:34:56Z")でシリアライズされます。

UTC表記が基本で、タイムゾーン情報も含まれます。

  • Newtonsoft.Jsonのデフォルト

旧来の\/Date(Unixタイムスタンプ)\/形式(例: "/Date(1654083296000)/")が使われることもありますが、設定によりISO 8601形式に変更可能です。

タイムゾーンの扱いも柔軟に設定できます。

この差異により、異なるライブラリ間でJSONをやり取りすると、DateTimeの解釈がずれることがあります。

対策としては以下が挙げられます。

  • 明示的にフォーマットを指定する

カスタムコンバーターやJsonSerializerOptionsJsonSerializerSettingsでフォーマットを統一します。

  • UTCで統一する

アプリケーション全体でDateTimeをUTCに統一し、タイムゾーンの混乱を避けます。

  • テストで検証する

シリアライズ・デシリアライズの結果を確認し、期待通りの日時が扱われているか検証します。

Unicodeとエンコーディング問題

JSONはUTF-8を標準としていますが、実際のエンコーディングやUnicode文字の扱いで問題が発生することがあります。

  • エンコーディングの不一致

JSON文字列を読み書きする際に、UTF-8以外のエンコーディング(例: Shift_JISやUTF-16)を使うと文字化けや例外が発生します。

必ずUTF-8で処理することが推奨されます。

  • Unicodeエスケープの扱い

特殊文字や絵文字などは\uXXXX形式でエスケープされることがあります。

System.Text.JsonはデフォルトでHTMLエスケープも行うため、<>などが\u003cのように変換されることがあります。

  • エスケープの制御

System.Text.JsonJsonSerializerOptions.EncoderJavaScriptEncoder.UnsafeRelaxedJsonEscapingを設定すると、エスケープを緩和できます。

var options = new JsonSerializerOptions
{
    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
  • Newtonsoft.Jsonのエスケープ設定

StringEscapeHandlingプロパティでエスケープの挙動を制御可能です。

これらの設定を適切に行わないと、Webページでの表示崩れやAPI間のデータ不整合が起こることがあります。

Enumのシリアライズ方式

列挙型enumのシリアライズ方法もライブラリによって異なり、意図しない形式で出力されることがあります。

  • 数値としてシリアライズ

デフォルトでは、enumは整数値としてシリアライズされます。

例えば、Color.Red = 1の場合、1としてJSONに出力されます。

  • 文字列としてシリアライズ

可読性やAPI仕様のために文字列で出力したい場合、System.Text.JsonではJsonStringEnumConverterJsonSerializerOptions.Convertersに追加します。

using System.Text.Json.Serialization;
var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());
  • Newtonsoft.Jsonの場合

StringEnumConverterJsonSerializerSettings.Convertersに追加するか、属性[JsonConverter(typeof(StringEnumConverter))]を付与します。

using Newtonsoft.Json.Converters;
var settings = new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter());
  • 注意点

文字列シリアライズにすると、APIの互換性やクライアント側のパース処理に影響するため、仕様に合わせて使い分ける必要があります。

これらの違いを理解し、プロジェクトの要件に応じて適切に設定しないと、データの不整合やバグの原因となることがあります。

関連ツールと拡張

JsonSchema生成と検証

JSON Schemaは、JSONデータの構造や型、制約を定義するための仕様であり、データの検証やドキュメント生成に役立ちます。

C#のオブジェクトからJSON Schemaを自動生成したり、JSONデータがSchemaに準拠しているか検証したりするツールやライブラリが多数存在します。

  • NJsonSchema

C#のクラスからJSON Schemaを生成できるライブラリです。

逆にJSON SchemaからC#のクラスを生成することも可能で、APIクライアントの自動生成などに活用されています。

検証機能も備えており、JSONデータのバリデーションに利用できます。

  • Newtonsoft.Json.Schema

Newtonsoft.Jsonの拡張で、JSON Schemaの生成と検証をサポートします。

Schemaを使ってJSONの構造を厳密にチェックできるため、APIの入力検証やデータ整合性の確保に有効です。

  • System.Text.Json.Schema(開発中)

MicrosoftはSystem.Text.Json向けのSchemaサポートを開発中で、将来的には標準的なSchema生成・検証機能が提供される予定です。

JSON Schemaを活用することで、JSONデータの品質を保ち、異なるシステム間でのデータ交換の信頼性を高められます。

ソースジェネレーターによる高速化

.NET 5以降で導入されたソースジェネレーターは、コンパイル時にコードを自動生成する機能で、JSONシリアライズの高速化に活用されています。

  • System.Text.Jsonのソースジェネレーター

System.Text.Jsonは、JsonSerializerContextを使ったソースジェネレーターを提供しています。

これにより、リフレクションを使わずにコンパイル時にシリアライズ・デシリアライズコードを生成し、実行時のパフォーマンスを大幅に向上させます。

  • 使い方の例
  1. 対象のクラスに対してJsonSerializable属性を付与したコンテキストクラスを作成。
  2. コンパイル時に専用コードが生成され、JsonSerializerの呼び出しで高速な処理が可能に。
  • メリット
    • 実行時のリフレクションコストが削減されます
    • メモリ使用量が減少し、GC負荷も軽減
    • 大規模データや高頻度処理で効果的
  • 注意点
    • ソースジェネレーターの導入には.NET 5以降が必要でしょう
    • 生成コードの管理やビルド時間への影響を考慮する必要があります

Visual Studio拡張機能

Visual Studioには、JSONシリアライズ関連の開発を支援する拡張機能が多数あります。

これらを活用することで、開発効率やコード品質を向上させられます。

  • JSON Viewer / Editor

JSONファイルの構文ハイライトや折りたたみ、整形機能を提供し、読みやすく編集しやすくします。

  • Json2CSharp

JSONサンプルからC#のクラス定義を自動生成する拡張。

APIレスポンスのモデル作成が簡単になります。

  • System.Text.Json Source Generator Support

ソースジェネレーターを使ったSystem.Text.Jsonのコード生成を支援するツールやテンプレートが提供されています。

  • ResharperやCodeRushのJSONサポート

これらのコード解析ツールは、JSON関連のコード補完やリファクタリング支援を行い、ミスを減らします。

これらの拡張機能を活用することで、JSONシリアライズに関わる作業を効率化し、バグの早期発見や保守性の向上につなげられます。

.NETバージョン別対応状況

.NET Frameworkでの選択肢

.NET Framework環境では、標準でSystem.Text.Jsonが利用できないため、JSONシリアライズには主にNewtonsoft.Json(Json.NET)が選択されます。

Newtonsoft.Jsonは.NET Framework 3.5以降に対応しており、豊富な機能と高い互換性を持つため、レガシーなプロジェクトや既存の大規模システムで広く使われています。

  • メリット
    • 豊富な機能(カスタムコンバーター、多態性対応、LINQ to JSONなど)
    • 安定した動作と長期間のサポート
    • 多くのサードパーティライブラリやフレームワークとの互換性
  • 注意点
    • パフォーマンスはSystem.Text.Jsonに比べてやや劣る場合がある
    • 追加のNuGetパッケージが必要

.NET Frameworkで新規にJSON処理を導入する場合は、基本的にNewtonsoft.Jsonを利用するのが現実的です。

.NET Coreでの推奨設定

.NET Core 3.0以降では、System.Text.Jsonが標準ライブラリとして組み込まれ、推奨されるJSONシリアライズ手段となっています。

軽量かつ高速であり、追加の依存なしに利用できるため、パフォーマンス重視のアプリケーションに適しています。

  • 推奨設定例
    • JsonSerializerOptionsWriteIndentedPropertyNamingPolicy(例:JsonNamingPolicy.CamelCase)を設定し、API仕様に合わせる
    • 必要に応じてカスタムコンバーターを登録
    • ASP.NET CoreではデフォルトでSystem.Text.Jsonが使われるため、特別な設定が不要な場合が多い
  • Newtonsoft.Jsonの利用

互換性や高度な機能が必要な場合は、Microsoft.AspNetCore.Mvc.NewtonsoftJsonパッケージを導入して切り替え可能です。

  • 注意点
    • System.Text.Jsonは一部機能が限定的なため、複雑なシリアライズ要件がある場合はNewtonsoft.Jsonを検討
    • .NET CoreのバージョンによってSystem.Text.Jsonの機能が進化しているため、最新のドキュメントを参照することが重要

.NET 5以降の最新機能

.NET 5以降では、System.Text.Jsonがさらに強化され、多くの新機能が追加されています。

これにより、より多様なシリアライズ要件に対応可能となり、パフォーマンスも向上しています。

  • ソースジェネレーターのサポート

コンパイル時にシリアライズコードを生成し、リフレクションを排除して高速化を実現。

大規模データや高頻度処理で効果的です。

  • 循環参照のサポート

ReferenceHandler.Preserveを使い、循環参照を安全にシリアライズ・デシリアライズ可能に。

  • 拡張されたカスタムコンバーター機能

ジェネリック型や複雑な型に対応したカスタムコンバーターの実装が容易に。

  • 改善されたパフォーマンスとメモリ効率

バッファ管理や非同期APIの最適化により、より高速かつ低メモリで動作。

  • JSON Pathやスキーマ検証の拡張(今後の予定含む)

JSONの動的操作や検証機能の強化が進行中。

  • 互換性の向上

Newtonsoft.Jsonとの機能差を縮小しつつ、標準ライブラリとしての利便性を高めています。

.NET 5以降の環境では、可能な限りSystem.Text.Jsonの最新機能を活用し、パフォーマンスと保守性の両立を図ることが推奨されます。

まとめ

この記事では、C#でのJSONシリアライズにおけるSystem.Text.JsonNewtonsoft.Jsonの基本操作や特徴、カスタマイズ方法、シナリオ別活用例、エラーハンドリングやセキュリティ対策、性能最適化のポイントまで幅広く解説しました。

各ライブラリの違いや.NETバージョン別の対応状況を理解し、用途や環境に応じて最適な選択と設定を行うことで、安全かつ効率的なJSON処理が実現できます。

関連記事

Back to top button
目次へ