【C#】System.Text.JsonとNewtonsoft.Jsonで学ぶJSONシリアライズの基本と活用法
JSONシリアライズはC#オブジェクトをテキスト化して保存や通信を簡単にする手段です。
標準のSystem.Text.Json
は軽量で速く、Newtonsoft.Json
は機能が豊富です。
用途が簡単なら前者、複雑なマッピングやカスタム変換が必要なら後者が適しています。
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.Json
やNewtonsoft.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の読み書きに特化したUtf8JsonReader
やUtf8JsonWriter
といった構造体を提供しており、これらは低レベルのストリーム処理を効率的に行うための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を人間が読みやすい形で出力したい場合は、JsonSerializerOptions
のWriteIndented
プロパティを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仕様や他システムとの連携で命名規則を変えたい場合があります。
JsonSerializerOptions
のPropertyNamingPolicy
プロパティに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を標準としていますが、JsonSerializerOptions
のEncoder
プロパティでエンコーディングの挙動を制御できます。
例えば、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));
}
}
このコンバーターは、DateTime
をyyyy-MM-dd
形式の文字列としてシリアライズし、同じ形式でデシリアライズします。
属性での適用方法
作成したカスタムコンバーターは、対象のプロパティやクラスに[JsonConverter(typeof(ConverterType))]
属性を付けて適用できます。
public class Event
{
public string Name { get; set; }
[JsonConverter(typeof(DateTimeConverter))]
public DateTime Date { get; set; }
}
このように属性を使うと、特定のプロパティだけに変換ルールを限定できます。
オプション経由での登録
また、JsonSerializerOptions
のConverters
コレクションにカスタムコンバーターを追加して、グローバルに適用することも可能です。
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
循環参照があるオブジェクトをシリアライズする際の挙動を制御します。
Ignore
やError
、Serialize
などのオプションがあり、無限ループを防止できます。
TypeNameHandling
オブジェクトの型情報をJSONに埋め込むかどうかを設定します。
多態性のあるオブジェクトを正しく復元する際に便利ですが、セキュリティリスクもあるため注意が必要です。
これらの設定は、JsonSerializerSettings
クラスでまとめて指定し、JsonConvert.SerializeObject
やJsonConvert.DeserializeObject
の引数として渡します。
細かい制御が必要なシナリオにおいて、非常に強力なツールとなります。
柔軟なデータ変換
Newtonsoft.Json
は、標準で多くの型をサポートするだけでなく、カスタムの変換ロジックを簡単に実装できる点が優れています。
JsonConverter
クラスを継承して独自のシリアライズ・デシリアライズ処理を定義でき、複雑なデータ構造や特殊なフォーマットにも対応可能です。
例えば、日付のフォーマットを独自に指定したり、列挙型を文字列として扱ったり、特定のプロパティを条件付きでシリアライズしたりといった柔軟な制御が行えます。
また、JsonConverter
は属性で特定のクラスやプロパティに適用できるほか、JsonSerializerSettings
のConverters
コレクションに登録してグローバルに適用することも可能です。
さらに、Newtonsoft.Json
は多態性のあるオブジェクトのシリアライズに強く、TypeNameHandling
を活用することで、派生クラスの情報を保持したままJSONに変換し、正確に復元できます。
LINQ to JSONの提供
Newtonsoft.Json
は、JSONデータを動的に操作できるLINQ to JSON
機能を提供しています。
JObject
やJArray
といったクラスを使うことで、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.All
やTypeNameHandling.Auto
を使うと、JSONに$type
プロパティが追加され、復元時に型を特定できます- しかし、悪意のあるJSONを受け取ると、任意の型のインスタンス生成につながる恐れがあり、リモートコード実行の脆弱性を招く可能性があります
安全に使うためには、信頼できるデータのみで使用し、SerializationBinder
で許可する型を制限することが推奨されます。
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
SerializationBinder = new KnownTypesBinder() // 独自実装で許可型を制限
};
カスタムJsonConverter
JsonConverter
を継承して独自のシリアライズ・デシリアライズ処理を実装できます。
これにより、標準では対応できない特殊な型やフォーマットを扱えます。
簡易例の実装
以下は、DateTime
をyyyy-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; }
}
リーダー/ライターの詳細制御
JsonReader
とJsonWriter
を使うことで、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を使ってJObject
やJArray
の要素を検索・操作できます。
例えば、配列内の特定の要素を取得したり、プロパティを追加・削除したりできます。
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.Json
とNewtonsoft.Json
のパフォーマンスを比較する際には、主に以下のポイントに注目します。
- シリアライズ速度
オブジェクトをJSON文字列に変換する速度は、APIレスポンスの生成やファイル書き出しの効率に直結します。
System.Text.Json
は低レベルのバイト操作を活用しており、一般的に高速です。
一方、Newtonsoft.Json
は多機能ゆえに若干のオーバーヘッドがあります。
- デシリアライズ速度
JSON文字列からオブジェクトに復元する速度も重要です。
特に大量データや高頻度の処理では、System.Text.Json
の高速なパース性能が有利です。
- メモリ使用量
メモリ効率はサーバーのスケーラビリティに影響します。
System.Text.Json
はSpan<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
の機能を活用したい場合や互換性のために切り替えが必要な場合に有効です。
切り替え手順は以下の通りです。
- NuGetで
Microsoft.AspNetCore.Mvc.NewtonsoftJson
パッケージをインストールします。 Startup.cs
(またはProgram.cs
)のConfigureServices
メソッドで、AddControllers
にAddNewtonsoftJson
を追加します。
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.Deserialize
やUtf8JsonReader
を使った読み取り時に不正な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#のプロパティ型が一致しない場合、デシリアライズ時に例外が発生したり、意図しない動作になることがあります。
これを防ぐための対策を紹介します。
- 柔軟な型指定
プロパティの型をstring
やobject
、dynamic
にすることで、型の違いによる例外を回避できます。
ただし、型安全性は低下します。
- カスタムコンバーターの利用
型変換のルールをカスタムコンバーターで定義し、異なる型のデータを適切に変換します。
例えば、数値が文字列として送られてくる場合に対応可能です。
JsonSerializerOptions
やJsonSerializerSettings
の設定
例えば、System.Text.Json
のJsonSerializerOptions
でNumberHandling
を設定し、数値と文字列の相互変換を許可できます。
var options = new JsonSerializerOptions
{
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString
};
- 事前バリデーション
JSONを受け取る前にスキーマ検証やJSON Schemaを使ったバリデーションを行い、型の不一致を早期に検出します。
循環参照の回避策
オブジェクトのプロパティが相互に参照し合う循環参照がある場合、シリアライズ時に無限ループが発生し、スタックオーバーフローや例外の原因となります。
これを回避する方法を説明します。
Newtonsoft.Json
のReferenceLoopHandling
設定
JsonSerializerSettings
のReferenceLoopHandling
をIgnore
に設定すると、循環参照のプロパティを無視してシリアライズします。
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
string json = JsonConvert.SerializeObject(obj, settings);
System.Text.Json
のReferenceHandler
設定
.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.Json
のTypeNameHandling
設定で型名を埋め込む場合、悪意のあるJSONにより任意の型のインスタンス生成が可能となり、リモートコード実行(RCE)などの深刻な脆弱性を引き起こす恐れがあります。
例えば、TypeNameHandling.All
を有効にした状態で信頼できないJSONをデシリアライズすると、攻撃者が悪意のある型を指定してシステム内部のコードを実行させることが可能です。
対策としては以下が挙げられます。
- 信頼できるデータのみをデシリアライズする
外部からの入力を直接デシリアライズしないことが基本です。
SerializationBinder
ISerializationBinder
で許可する型を制限する
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.EscapeHtml
をJsonSerializerSettings
のStringEscapeHandling
に設定します。
var settings = new JsonSerializerSettings
{
StringEscapeHandling = StringEscapeHandling.EscapeHtml
};
- JSONを直接HTMLに埋め込まない
JSONはJavaScriptの変数として安全に扱うか、API経由で取得して動的に処理する方法が望ましいです。
デシリアライズ攻撃の防御
デシリアライズ攻撃は、悪意のあるJSONデータを利用してアプリケーションの挙動を不正に操作する攻撃手法です。
代表的な攻撃には、前述の型名埋め込みを悪用したリモートコード実行や、オブジェクトの状態を不正に変更するものがあります。
防御策は以下の通りです。
- 信頼できるデータのみをデシリアライズする
外部からの入力は必ず検証・サニタイズし、信頼できないデータは処理しない。
- 型の制限
Newtonsoft.Json
のSerializationBinder
やSystem.Text.Json
のカスタムコンバーターで、許可された型のみを復元するよう制限します。
- 最小限の権限で実行
アプリケーションの実行環境を最小権限に設定し、万が一攻撃が成功しても被害を抑えます。
- 最新のライブラリを使用する
セキュリティパッチが適用された最新版のJSONライブラリを利用します。
- デシリアライズ時の例外処理
不正なデータを検出したら例外をキャッチし、適切に処理します。
これらの対策を組み合わせて、JSONのシリアライズ・デシリアライズに伴うセキュリティリスクを低減してください。
性能最適化のヒント
オブジェクトプール活用
JSONのシリアライズやデシリアライズ処理では、多数の一時的なオブジェクトが生成されることが多く、これがGC(ガベージコレクション)の負荷増大やパフォーマンス低下の原因となります。
これを軽減するために、オブジェクトプールを活用する方法があります。
オブジェクトプールとは、使い回し可能なオブジェクトのプール(再利用可能なストック)を用意し、新規生成のコストを削減する仕組みです。
特にUtf8JsonWriter
やUtf8JsonReader
のような低レベル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.Json
やNewtonsoft.Json
は、ストリームを使った非同期のシリアライズ・デシリアライズAPIを提供しています。
非同期処理を使うと、ファイルやネットワークからの読み書き中にスレッドをブロックせず、他の処理を並行して実行できます。
これにより、サーバーの応答性が向上し、全体の処理効率が改善します。
例えば、System.Text.Json
のDeserializeAsync<T>
やSerializeAsync
を使うと、FileStream
やNetworkStream
と組み合わせて効率的に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.Json
のUtf8JsonWriter
やUtf8JsonReader
では、バッファを自前で用意して渡すことができるため、バッファサイズを明示的に制御可能です。
ArrayPool<byte>
と組み合わせてバッファを再利用しつつ、適切なサイズを選ぶと効率的です。
また、JsonSerializerOptions
のDefaultBufferSize
(.NET 7以降)などの設定項目でバッファサイズを調整できる場合もあります。
バッファサイズ調整のポイントは以下の通りです。
- 小さすぎるバッファは避ける
頻繁なバッファ拡張やI/Oが発生し、パフォーマンス低下の原因となります。
- 大きすぎるバッファはメモリ浪費になる
特に多数の同時処理がある場合は注意が必要でしょう。
- 実際のデータサイズに合わせて調整する
処理対象のJSONの平均サイズや最大サイズを考慮し、適切なバッファサイズを選択します。
- プロファイリングで効果を検証する
実際のアプリケーションでパフォーマンス計測を行い、最適なバッファサイズを見極める。
これらを踏まえ、バッファサイズを適切に設定することで、JSON処理の効率を最大化できます。
よくある落とし穴
DateTimeフォーマット差異
JSONシリアライズにおけるDateTime
の扱いは、ライブラリ間で挙動が異なるため注意が必要です。
System.Text.Json
とNewtonsoft.Json
ではデフォルトのフォーマットやタイムゾーンの扱いに差異があります。
System.Text.Json
のデフォルト
ISO 8601形式の文字列(例: "2024-06-01T12:34:56Z"
)でシリアライズされます。
UTC表記が基本で、タイムゾーン情報も含まれます。
Newtonsoft.Json
のデフォルト
旧来の\/Date(Unixタイムスタンプ)\/
形式(例: "/Date(1654083296000)/"
)が使われることもありますが、設定によりISO 8601形式に変更可能です。
タイムゾーンの扱いも柔軟に設定できます。
この差異により、異なるライブラリ間でJSONをやり取りすると、DateTime
の解釈がずれることがあります。
対策としては以下が挙げられます。
- 明示的にフォーマットを指定する
カスタムコンバーターやJsonSerializerOptions
、JsonSerializerSettings
でフォーマットを統一します。
- 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.Json
のJsonSerializerOptions.Encoder
にJavaScriptEncoder.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
ではJsonStringEnumConverter
をJsonSerializerOptions.Converters
に追加します。
using System.Text.Json.Serialization;
var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());
Newtonsoft.Json
の場合
StringEnumConverter
をJsonSerializerSettings.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
を使ったソースジェネレーターを提供しています。
これにより、リフレクションを使わずにコンパイル時にシリアライズ・デシリアライズコードを生成し、実行時のパフォーマンスを大幅に向上させます。
- 使い方の例
- 対象のクラスに対して
JsonSerializable
属性を付与したコンテキストクラスを作成。 - コンパイル時に専用コードが生成され、
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シリアライズ手段となっています。
軽量かつ高速であり、追加の依存なしに利用できるため、パフォーマンス重視のアプリケーションに適しています。
- 推奨設定例
JsonSerializerOptions
でWriteIndented
やPropertyNamingPolicy
(例: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.Json
とNewtonsoft.Json
の基本操作や特徴、カスタマイズ方法、シナリオ別活用例、エラーハンドリングやセキュリティ対策、性能最適化のポイントまで幅広く解説しました。
各ライブラリの違いや.NETバージョン別の対応状況を理解し、用途や環境に応じて最適な選択と設定を行うことで、安全かつ効率的なJSON処理が実現できます。