【C#】JSON文字列をSerialize・Deserializeで高速変換する方法と実用例
JSON文字列はC#でSystem.Text.Json
とNewtonsoft.Json
が中心となり、Serialize
でオブジェクトを文字列化しDeserialize
で逆変換できます。
属性でキー名変更やプロパティ除外も柔軟に設定できるため、外部APIとのやり取りをスムーズに行えます。
- JSON処理ライブラリの選択肢
- 基本的なシリアライズ手順
- 基本的なデシリアライズ手順
- 属性による変換ルールの制御
- カスタムコンバーター
- コレクションとDictionaryの変換
- DateTimeと日時フォーマット
- エンコーディングとUTF-8
- 非同期APIの利用
- 高速化の考慮
- オンラインAPI連携
- 動的型とJsonDocument
- System.Text.JsonとNewtonsoft.Jsonの比較
- トラブルシューティング
- セキュリティ考慮
- バージョン別の新機能
- サンプルコード集
- 基本シリアライズ例
- 属性付き変換例
- カスタムコンバーター例
- HTTP通信例
- Q1: System.Text.JsonとNewtonsoft.Jsonのどちらを使うべきですか?
- Q2: JSONのプロパティ名を小文字にしたいのですが、どうすればよいですか?
- Q3: JSONのデシリアライズ時にプロパティ名の大文字小文字を無視したいです。
- Q4: 循環参照があるオブジェクトをシリアライズするとエラーになります。どうすればよいですか?
- Q5: JSONの一部だけを動的に読み取りたい場合はどうすればよいですか?
- Q6: カスタムコンバーターはどのように作成しますか?
- Q7: 非同期でJSONを読み書きするにはどうすればよいですか?
- Q8: JSONのシリアライズ結果にインデントを付けるには?
- Q9: JSONのプロパティをシリアライズから除外したい場合は?
- Q10: 日本語を含むJSONは正しく扱えますか?
- まとめ
JSON処理ライブラリの選択肢
C#でJSON文字列を高速かつ効率的に変換するには、適切なJSON処理ライブラリを選ぶことが重要です。
代表的なライブラリとしては、.NET標準のSystem.Text.Json
と、長年にわたり広く使われているNewtonsoft.Json
(別名Json.NET)があります。
ここでは、それぞれの特徴や使い方、違いについて詳しく解説いたします。
System.Text.Json
System.Text.Json
は、.NET Core 3.0以降および.NET 5以降で標準搭載されているJSON処理ライブラリです。
Microsoftが公式に提供しており、軽量かつ高速なシリアライズ・デシリアライズを実現しています。
特徴
- 高速処理
System.Text.Json
は、パフォーマンスを重視して設計されており、特に大規模なJSONデータの処理において高速な変換が可能です。
内部でUTF-8エンコーディングを直接扱うため、余計な文字列変換が少なく効率的です。
- 軽量で依存性が少ない
.NET標準の一部として提供されているため、追加の外部パッケージをインストールする必要がありません。
これにより、プロジェクトの依存関係を減らせます。
- 非同期APIの充実
ストリームを使った非同期のシリアライズ・デシリアライズメソッドが用意されており、I/O操作と組み合わせて効率的に処理できます。
- 属性による細かい制御
JsonPropertyName
やJsonIgnore
などの属性を使い、プロパティ名のカスタマイズやシリアライズ対象の制御が可能です。
- カスタムコンバーターのサポート
独自の型変換ロジックを実装できるカスタムコンバーターを作成し、特殊なデータ型のシリアライズ・デシリアライズに対応できます。
基本的な使い方
using System;
using System.Text.Json;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main()
{
// JSON文字列をオブジェクトに変換(デシリアライズ)
string jsonString = "{\"Name\":\"Alice\",\"Age\":30}";
Person person = JsonSerializer.Deserialize<Person>(jsonString);
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
// オブジェクトをJSON文字列に変換(シリアライズ)
string serialized = JsonSerializer.Serialize(person);
Console.WriteLine(serialized);
}
}
Name: Alice, Age: 30
{"Name":"Alice","Age":30}
この例では、JsonSerializer.Deserialize<T>
でJSON文字列をPerson
オブジェクトに変換し、JsonSerializer.Serialize
でオブジェクトをJSON文字列に戻しています。
Newtonsoft.Json
Newtonsoft.Json
は、通称Json.NETとも呼ばれ、C#で最も広く使われているJSON処理ライブラリの一つです。
長年にわたり多くのプロジェクトで採用されており、豊富な機能と柔軟なカスタマイズ性が特徴です。
特徴
- 豊富な機能
JSONのシリアライズ・デシリアライズだけでなく、LINQ to JSONによる動的なJSON操作、JSONスキーマの検証、カスタムシリアライザーの作成など、多彩な機能を備えています。
- 柔軟なカスタマイズ
属性や設定オプションを使って、プロパティ名の変更、無視、Null値の扱い、循環参照の制御など細かい挙動を調整できます。
- 互換性の高さ
古い.NET Frameworkから最新の.NET 6/7まで幅広く対応しており、レガシーな環境でも利用しやすいです。
- サードパーティ製品との連携
多くのライブラリやフレームワークがNewtonsoft.Json
を標準のJSON処理ライブラリとして採用しているため、連携がスムーズです。
基本的な使い方
using System;
using Newtonsoft.Json;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main()
{
// JSON文字列をオブジェクトに変換(デシリアライズ)
string jsonString = "{\"Name\":\"Alice\",\"Age\":30}";
Person person = JsonConvert.DeserializeObject<Person>(jsonString);
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
// オブジェクトをJSON文字列に変換(シリアライズ)
string serialized = JsonConvert.SerializeObject(person);
Console.WriteLine(serialized);
}
}
Name: Alice, Age: 30
{"Name":"Alice","Age":30}
このコードでは、JsonConvert.DeserializeObject<T>
でJSON文字列をオブジェクトに変換し、JsonConvert.SerializeObject
でオブジェクトをJSON文字列に変換しています。
主な違い
System.Text.Json
とNewtonsoft.Json
はどちらもJSONのシリアライズ・デシリアライズを行うライブラリですが、いくつかの重要な違いがあります。
以下の表に主な違いをまとめました。
項目 | System.Text.Json | Newtonsoft.Json |
---|---|---|
標準搭載 | .NET Core 3.0以降、.NET 5以降に標準搭載 | 外部パッケージ(NuGet)として提供 |
パフォーマンス | 高速で軽量 | 比較的遅いが機能豊富 |
機能の豊富さ | 基本的な機能に特化 | 多機能(LINQ to JSON、スキーマ検証など) |
カスタマイズ性 | 属性やカスタムコンバーターで対応可能 | 属性、設定、カスタムシリアライザーが豊富 |
非同期API | 充実している | 非同期APIは限定的 |
互換性 | .NET Core以降に最適化 | .NET Frameworkから最新まで対応 |
動的JSON操作 | JsonDocument やJsonElement で対応 | JObject やJToken で柔軟に操作可能 |
循環参照の扱い | デフォルトでは例外をスロー | 循環参照を許可する設定が可能 |
コミュニティとサポート | Microsoft公式サポート | 大規模なコミュニティと豊富なドキュメント |
選択のポイント
- パフォーマンス重視で、標準機能で十分な場合は
System.Text.Json
がおすすめです。特に新規プロジェクトや.NET Core/.NET 5以降の環境での利用に適しています - 高度な機能や柔軟なカスタマイズが必要な場合や、既存の
Newtonsoft.Json
ベースのコードがある場合は、Newtonsoft.Json
を使い続けるのが良いでしょう - 動的なJSON操作や複雑なスキーマ検証が必要な場合は、
Newtonsoft.Json
の方が使いやすいです - 非同期処理を多用する場合は、
System.Text.Json
の非同期APIが便利です
以上の特徴を踏まえ、プロジェクトの要件や環境に合わせて最適なライブラリを選択してください。
どちらのライブラリもC#でのJSON処理において強力なツールであり、適切に使い分けることで高速かつ安定したJSON変換を実現できます。
基本的なシリアライズ手順
デフォルトシリアライズ
POCOクラス定義
JSONへのシリアライズ対象となるクラスは、通常POCO(Plain Old CLR Object)として定義します。
これは、特別な継承やインターフェースを必要とせず、単純にプロパティを持つクラスです。
シリアライズ時には、パブリックなプロパティがJSONのキーと値に変換されます。
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
このPerson
クラスは、Name
とAge
という2つのプロパティを持ちます。
これがJSONのキーとして使われ、値が対応します。
Serializeメソッド呼び出し
System.Text.Json
を使ったシリアライズは、JsonSerializer.Serialize
メソッドを呼び出すだけで簡単に行えます。
引数にシリアライズしたいオブジェクトを渡すと、JSON文字列が返されます。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
Person person = new Person { Name = "Alice", Age = 30 };
// オブジェクトをJSON文字列に変換
string jsonString = JsonSerializer.Serialize(person);
Console.WriteLine(jsonString);
}
}
{"Name":"Alice","Age":30}
この例では、Person
オブジェクトが{"Name":"Alice","Age":30}
というJSON文字列に変換されています。
デフォルトでは、プロパティ名はそのままJSONのキーとして使われ、値は対応する型に応じて変換されます。
出力フォーマットの調整
インデント有無
デフォルトのSerialize
メソッドは、コンパクトなJSON文字列を生成します。
読みやすさを重視してインデント(改行やスペース)を付けたい場合は、JsonSerializerOptions
のWriteIndented
プロパティをtrue
に設定します。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
Person person = new Person { Name = "Alice", Age = 30 };
var options = new JsonSerializerOptions
{
WriteIndented = true // インデント付きで出力
};
string jsonString = JsonSerializer.Serialize(person, options);
Console.WriteLine(jsonString);
}
}
{
"Name": "Alice",
"Age": 30
}
このように、インデントを付けることでJSONが見やすくなります。
ログ出力やデバッグ時に便利です。
ただし、ファイルサイズが大きくなるため、通信やストレージの効率を重視する場合はインデントなしが推奨されます。
プロパティ名の大文字小文字
System.Text.Json
では、デフォルトでC#のプロパティ名がそのままJSONのキー名になります。
つまり、Name
は"Name"
、Age
は"Age"
として出力されます。
JSONのキー名をすべて小文字にしたい場合や、特定の命名規則に合わせたい場合は、JsonSerializerOptions
のPropertyNamingPolicy
を設定します。
例えば、キャメルケース(先頭小文字)に変換するにはJsonNamingPolicy.CamelCase
を指定します。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
Person person = new Person { Name = "Alice", Age = 30 };
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string jsonString = JsonSerializer.Serialize(person, options);
Console.WriteLine(jsonString);
}
}
{"name":"Alice","age":30}
このように、Name
が"name"
、Age
が"age"
に変換されます。
APIの仕様や他システムとの連携でキー名の形式が決まっている場合に役立ちます。
なお、PropertyNamingPolicy
はシリアライズ・デシリアライズ両方に影響します。
デシリアライズ時も同じポリシーを指定すると、JSONのキー名とC#のプロパティ名のマッピングが正しく行われます。
これらの基本的なシリアライズ手順を押さえることで、C#オブジェクトとJSON文字列の変換をスムーズに行えます。
次のステップでは、より細かい制御やカスタマイズについて解説していきます。
基本的なデシリアライズ手順
文字列からオブジェクト取得
JSON文字列をC#のオブジェクトに変換するには、System.Text.Json
のJsonSerializer.Deserialize<T>
メソッドを使います。
ジェネリック型パラメーターT
に変換先のクラスを指定し、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()
{
string jsonString = "{\"Name\":\"Alice\",\"Age\":30}";
// JSON文字列をPersonオブジェクトに変換
Person person = JsonSerializer.Deserialize<Person>(jsonString);
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
Name: Alice, Age: 30
この例では、jsonString
に格納されたJSONをPerson
型に変換し、プロパティの値を取得しています。
JSONのキー名とC#のプロパティ名が一致している場合、特別な設定なしで正しくマッピングされます。
エラーハンドリング
デシリアライズ時には、JSONの形式が正しくない場合や、型の不一致がある場合に例外が発生することがあります。
これらの例外を適切に処理し、アプリケーションの安定性を保つことが重要です。
例外種類と対策
主に発生しやすい例外は以下の通りです。
例外名 | 発生原因 | 対策例 |
---|---|---|
JsonException | JSONの構文エラーや不正な形式 | try-catchで捕捉し、エラーメッセージをログに記録する |
ArgumentNullException | 入力文字列がnull | 入力値の事前チェックを行う |
NotSupportedException | デシリアライズ対象の型がサポートされていない | 対応可能な型を使用するか、カスタムコンバーターを作成する |
InvalidOperationException | 型の不一致や変換できない値が含まれている | JSONの内容を検証し、型に合ったデータを渡す |
例外を捕捉する基本的なコード例は以下の通りです。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
string invalidJson = "{\"Name\":\"Alice\",\"Age\":\"thirty\"}"; // Ageが文字列で不正
try
{
Person person = JsonSerializer.Deserialize<Person>(invalidJson);
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
catch (JsonException ex)
{
Console.WriteLine($"JSONの解析エラー: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"その他のエラー: {ex.Message}");
}
}
}
JSONの解析エラー: The JSON value could not be converted to System.Int32. Path: $.Age | LineNumber: 0 | BytePositionInLine: 24.
この例では、Age
に文字列が入っているため、JsonException
が発生し、キャッチしてエラーメッセージを表示しています。
不正JSON検出
JSON文字列が不正な場合、JsonSerializer.Deserialize
はJsonException
をスローします。
例えば、括弧の閉じ忘れや不正な文字が含まれている場合です。
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 malformedJson = "{\"Name\":\"Alice\",\"Age\":30"; // 閉じ括弧がない
try
{
Person person = JsonSerializer.Deserialize<Person>(malformedJson);
}
catch (JsonException ex)
{
Console.WriteLine($"不正なJSONです: {ex.Message}");
}
}
}
不正なJSONです: '0' is an invalid end of a number. Expected a delimiter. Path: $.Age | LineNumber: 0 | BytePositionInLine: 24.
このように、JSONの構文エラーは例外メッセージに詳細が含まれているため、ログに記録して原因を特定しやすくなります。
また、JSONの形式が正しいかどうかを事前にチェックしたい場合は、JsonDocument.Parse
を使ってパースを試みる方法もあります。
パースに成功すればJSONは正しい形式と判断できます。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
string json = "{\"Name\":\"Alice\",\"Age\":30}";
try
{
// JsonDocument を生成しつつ、スコープ終了時に自動破棄する
using (JsonDocument doc = JsonDocument.Parse(json))
{
// ここでは特に処理せず、パースチェックだけ行う
}
Console.WriteLine("JSONは正しい形式です。");
}
catch (JsonException)
{
Console.WriteLine("JSONが不正です。");
}
}
}
JSONは正しい形式です。
この方法は、デシリアライズ前にJSONの妥当性を検証したい場合に有効です。
これらの手順と注意点を踏まえ、JSON文字列から安全かつ確実にC#オブジェクトを取得できるようにしましょう。
属性による変換ルールの制御
C#のJSONシリアライズ・デシリアライズでは、属性を使って変換ルールを細かく制御できます。
ここでは、プロパティ名のマッピング、プロパティの除外、Null値の扱い、既定値の制御について解説します。
プロパティ名のマッピング
C#のプロパティ名とJSONのキー名を異なる名前にしたい場合、System.Text.Json
ではJsonPropertyName
属性を使います。
これにより、シリアライズ時とデシリアライズ時のキー名をカスタマイズできます。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Person
{
[JsonPropertyName("full_name")]
public string Name { get; set; }
[JsonPropertyName("age_in_years")]
public int Age { get; set; }
}
class Program
{
static void Main()
{
Person person = new Person { Name = "Alice", Age = 30 };
string jsonString = JsonSerializer.Serialize(person);
Console.WriteLine(jsonString);
// JSON文字列をデシリアライズする際も属性が適用される
string jsonInput = "{\"full_name\":\"Bob\",\"age_in_years\":25}";
Person deserialized = JsonSerializer.Deserialize<Person>(jsonInput);
Console.WriteLine($"Name: {deserialized.Name}, Age: {deserialized.Age}");
}
}
{"full_name":"Alice","age_in_years":30}
Name: Bob, Age: 25
この例では、Name
プロパティがfull_name
、Age
プロパティがage_in_years
としてJSONに変換され、逆にJSONからも同じ名前でマッピングされます。
プロパティの除外
特定のプロパティをシリアライズやデシリアライズの対象から除外したい場合は、JsonIgnore
属性を使います。
これにより、JSONに含めたくない情報を簡単に除外できます。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Person
{
public string Name { get; set; }
[JsonIgnore]
public int Age { get; set; }
}
class Program
{
static void Main()
{
Person person = new Person { Name = "Alice", Age = 30 };
string jsonString = JsonSerializer.Serialize(person);
Console.WriteLine(jsonString);
// AgeはJSONに含まれないため、デシリアライズ時も無視される
string jsonInput = "{\"Name\":\"Bob\",\"Age\":25}";
Person deserialized = JsonSerializer.Deserialize<Person>(jsonInput);
Console.WriteLine($"Name: {deserialized.Name}, Age: {deserialized.Age}"); // Ageはデフォルト値0
}
}
{"Name":"Alice"}
Name: Bob, Age: 0
Age
プロパティはシリアライズ時に除外され、デシリアライズ時もJSONのAge
フィールドは無視されるため、Age
は初期値の0
になります。
Null値の扱い
デフォルトでは、null
のプロパティもJSONに含まれますが、JsonSerializerOptions
のDefaultIgnoreCondition
を設定することで、null
値のプロパティをシリアライズから除外できます。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Person
{
public string Name { get; set; }
public string? Nickname { get; set; }
}
class Program
{
static void Main()
{
Person person = new Person { Name = "Alice", Nickname = null };
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
string jsonString = JsonSerializer.Serialize(person, options);
Console.WriteLine(jsonString);
}
}
{"Name":"Alice"}
この例では、Nickname
がnull
のためJSONに含まれていません。
DefaultIgnoreCondition
には他にもNever
(常に書き込む)やWhenWritingDefault
(デフォルト値のときに除外)などがあります。
既定値の制御
C#のプロパティに既定値が設定されている場合、デフォルト値と同じ値のプロパティをシリアライズから除外したいことがあります。
System.Text.Json
では、JsonIgnoreCondition.WhenWritingDefault
を使うことで、既定値のプロパティを省略できます。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Person
{
public string Name { get; set; }
public int Age { get; set; } = 0;
public bool IsActive { get; set; } = true;
}
class Program
{
static void Main()
{
Person person = new Person { Name = "Alice", Age = 0, IsActive = true };
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
WriteIndented = true
};
string jsonString = JsonSerializer.Serialize(person, options);
Console.WriteLine(jsonString);
}
}
{
"Name": "Alice"
}
この例では、Age
が0
、IsActive
がtrue
(既定値)なので、両方ともJSONに含まれていません。
Name
だけが出力されています。
これらの属性を活用することで、JSONの構造や内容を柔軟に制御でき、API仕様やデータ連携の要件に合わせたシリアライズ・デシリアライズが可能になります。
カスタムコンバーター
System.Text.Json
では、標準のシリアライズ・デシリアライズ処理では対応できない特殊な型や独自の変換ルールを実装するために、カスタムコンバーターを作成できます。
ここでは、カスタムコンバーターの基本的な実装方法と適用方法について解説します。
変換ロジックの実装
カスタムコンバーターは、JsonConverter<T>
を継承したクラスとして実装します。
主にRead
メソッドとWrite
メソッドをオーバーライドして、デシリアライズとシリアライズのロジックを記述します。
Readメソッド
Read
メソッドは、JSONからC#オブジェクトに変換する処理を担当します。
引数のUtf8JsonReader
を使ってJSONの値を読み取り、目的の型のインスタンスを返します。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Temperature
{
public int Degrees { get; set; }
public bool IsCelsius { get; set; }
public override string ToString() => $"{Degrees}{(IsCelsius ? "C" : "F")}";
}
public class TemperatureConverter : JsonConverter<Temperature>
{
public override Temperature Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// JSONの値は文字列形式(例: "25C" または "77F")と想定
string value = reader.GetString();
int degrees = int.Parse(value.Substring(0, value.Length - 1));
bool isCelsius = value.EndsWith("C");
return new Temperature { Degrees = degrees, IsCelsius = isCelsius };
}
public override void Write(Utf8JsonWriter writer, Temperature value, JsonSerializerOptions options)
{
// Writeメソッドは後述
throw new NotImplementedException();
}
}
このRead
メソッドでは、JSONの文字列を取得し、末尾の文字で摂氏か華氏かを判別し、数値部分をパースしてTemperature
オブジェクトを生成しています。
Writeメソッド
Write
メソッドは、C#オブジェクトをJSONに変換する処理を担当します。
Utf8JsonWriter
を使ってJSONの値を書き込みます。
public override void Write(Utf8JsonWriter writer, Temperature value, JsonSerializerOptions options)
{
// Temperatureオブジェクトを "25C" のような文字列に変換して書き込む
writer.WriteStringValue(value.ToString());
}
Write
メソッドでは、Temperature
のToString
メソッドを使って文字列化し、そのままJSONの文字列値として書き込んでいます。
コンバーターの適用方法
作成したカスタムコンバーターは、対象のプロパティやクラスに適用する方法と、グローバルに登録する方法があります。
属性指定
特定のプロパティにだけカスタムコンバーターを適用したい場合は、JsonConverterAttribute
を使って属性指定します。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class WeatherForecast
{
public DateTime Date { get; set; }
[JsonConverter(typeof(TemperatureConverter))]
public Temperature Temperature { get; set; }
}
class Program
{
static void Main()
{
WeatherForecast forecast = new WeatherForecast
{
Date = DateTime.Now,
Temperature = new Temperature { Degrees = 25, IsCelsius = true }
};
string jsonString = JsonSerializer.Serialize(forecast);
Console.WriteLine(jsonString);
WeatherForecast deserialized = JsonSerializer.Deserialize<WeatherForecast>(jsonString);
Console.WriteLine($"Date: {deserialized.Date}, Temperature: {deserialized.Temperature}");
}
}
{"Date":"2024-06-01T12:00:00","Temperature":"25C"}
Date: 2024/06/01 12:00:00, Temperature: 25C
この例では、Temperature
プロパティに[JsonConverter(typeof(TemperatureConverter))]
を付けることで、TemperatureConverter
が適用されます。
グローバルオプション登録
プロジェクト全体で特定の型に対してカスタムコンバーターを適用したい場合は、JsonSerializerOptions
のConverters
コレクションに登録します。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
class Program
{
static void Main()
{
var options = new JsonSerializerOptions();
options.Converters.Add(new TemperatureConverter());
WeatherForecast forecast = new WeatherForecast
{
Date = DateTime.Now,
Temperature = new Temperature { Degrees = 25, IsCelsius = true }
};
string jsonString = JsonSerializer.Serialize(forecast, options);
Console.WriteLine(jsonString);
WeatherForecast deserialized = JsonSerializer.Deserialize<WeatherForecast>(jsonString, options);
Console.WriteLine($"Date: {deserialized.Date}, Temperature: {deserialized.Temperature}");
}
}
{"Date":"2024-06-01T12:00:00","Temperature":"25C"}
Date: 2024/06/01 12:00:00, Temperature: 25C
この方法では、Temperature
型のすべてのシリアライズ・デシリアライズにTemperatureConverter
が適用されます。
属性指定よりも一括管理しやすいのが特徴です。
カスタムコンバーターを活用することで、標準の変換ルールにない複雑なデータ形式や独自仕様のJSONを扱うことができ、柔軟なJSON処理が可能になります。
コレクションとDictionaryの変換
C#でJSONに変換する際、リストや配列などのコレクション型や、キーと値のペアを持つDictionary
型の扱いはよくあるケースです。
ここでは、リストのシリアライズ方法と、Dictionary
のキー変換ルールについて詳しく説明します。
リストのシリアライズ
C#のList<T>
や配列は、JSONでは配列[]
として表現されます。
System.Text.Json
のJsonSerializer.Serialize
を使うと、コレクションの各要素が順番にJSON配列の要素として変換されます。
using System;
using System.Collections.Generic;
using System.Text.Json;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main()
{
var people = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 25 }
};
string jsonString = JsonSerializer.Serialize(people, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonString);
}
}
[
{
"Name": "Alice",
"Age": 30
},
{
"Name": "Bob",
"Age": 25
}
]
この例では、List<Person>
がJSON配列に変換され、各Person
オブジェクトがJSONオブジェクトとして配列の要素になっています。
配列の順序は保持されます。
配列や他のコレクション型IEnumerable<T>
やT[]
も同様にシリアライズ可能です。
デシリアライズも同様に、JSON配列からList<T>
や配列に変換できます。
Dictionaryキーの変換ルール
Dictionary<TKey, TValue>
はJSONではオブジェクト{}
として表現されます。
キーはJSONのプロパティ名、値は対応する値として変換されます。
ただし、JSONのプロパティ名は文字列である必要があるため、Dictionary
のキーは文字列型であることが一般的です。
using System;
using System.Collections.Generic;
using System.Text.Json;
class Program
{
static void Main()
{
var dict = new Dictionary<string, int>
{
{ "apple", 3 },
{ "banana", 5 }
};
string jsonString = JsonSerializer.Serialize(dict, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonString);
}
}
{
"apple": 3,
"banana": 5
}
このように、Dictionary<string, int>
はJSONオブジェクトのプロパティとしてキーと値が表現されます。
非文字列キーの扱い
Dictionary
のキーが文字列以外の型の場合、System.Text.Json
はシリアライズ時に例外をスローします。
例えば、Dictionary<int, string>
はそのままではシリアライズできません。
using System;
using System.Collections.Generic;
using System.Text.Json;
class Program
{
static void Main()
{
var dict = new Dictionary<int, string>
{
{ 1, "one" },
{ 2, "two" }
};
try
{
string jsonString = JsonSerializer.Serialize(dict);
Console.WriteLine(jsonString);
}
catch (NotSupportedException ex)
{
Console.WriteLine($"エラー: {ex.Message}");
}
}
}
エラー: The collection type 'System.Collections.Generic.Dictionary`2[System.Int32,System.String]' is not supported.
この場合、キーを文字列に変換するカスタムコンバーターを作成するか、キーを文字列型に変換したDictionary<string, TValue>
を使う必要があります。
キーの変換例(カスタムコンバーター)
例えば、int
キーのDictionary
をJSONオブジェクトとしてシリアライズしたい場合、キーを文字列に変換するカスタムコンバーターを作成します。
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
public class IntKeyDictionaryConverter<TValue> : JsonConverter<Dictionary<int, TValue>>
{
public override Dictionary<int, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var dict = new Dictionary<int, TValue>();
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
return dict;
string propertyName = reader.GetString();
if (!int.TryParse(propertyName, out int key))
throw new JsonException($"キー '{propertyName}' は整数に変換できません。");
reader.Read();
TValue value = JsonSerializer.Deserialize<TValue>(ref reader, options);
dict.Add(key, value);
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, Dictionary<int, TValue> value, JsonSerializerOptions options)
{
writer.WriteStartObject();
foreach (var kvp in value)
{
writer.WritePropertyName(kvp.Key.ToString());
JsonSerializer.Serialize(writer, kvp.Value, options);
}
writer.WriteEndObject();
}
}
class Program
{
static void Main()
{
var dict = new Dictionary<int, string>
{
{ 1, "one" },
{ 2, "two" }
};
var options = new JsonSerializerOptions();
options.Converters.Add(new IntKeyDictionaryConverter<string>());
string jsonString = JsonSerializer.Serialize(dict, options);
Console.WriteLine(jsonString);
var deserialized = JsonSerializer.Deserialize<Dictionary<int, string>>(jsonString, options);
foreach (var kvp in deserialized)
{
Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");
}
}
}
{"1":"one","2":"two"}
Key: 1, Value: one
Key: 2, Value: two
このカスタムコンバーターを使うことで、int
キーのDictionary
をJSONオブジェクトとして扱えます。
リストや配列はJSON配列として自然に変換され、Dictionary<string, TValue>
はJSONオブジェクトとして扱われますが、キーが文字列以外の場合はカスタムコンバーターの利用が必要です。
これらのルールを理解して適切に使い分けることで、JSONとのデータ連携がスムーズになります。
DateTimeと日時フォーマット
C#でJSONに日時情報を含める場合、DateTime
型のシリアライズ・デシリアライズは特に注意が必要です。
日時のフォーマットによっては互換性や可読性に影響が出るため、適切な形式を選択・指定することが重要です。
ISO 8601形式
System.Text.Json
のデフォルトのDateTime
シリアライズ形式はISO 8601準拠の文字列です。
ISO 8601は国際標準の日時表記で、yyyy-MM-ddTHH:mm:ss.fffffffZ
のような形式になります。
タイムゾーンはZ
(UTC)やオフセット(例:+09:00
)で表されます。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
DateTime now = DateTime.UtcNow;
string jsonString = JsonSerializer.Serialize(now);
Console.WriteLine(jsonString);
}
}
"2024-06-01T03:45:30.1234567Z"
この例では、UTCの現在日時がISO 8601形式の文字列としてシリアライズされています。
DateTime
のKind
プロパティがUtc
の場合はZ
が付き、Local
の場合はオフセット付きの形式になります。
ISO 8601形式は多くのAPIやシステムで標準的に使われており、互換性が高いのが特徴です。
System.Text.Json
はこの形式を自動的に扱うため、特別な設定なしで利用できます。
カスタムフォーマット指定
標準のISO 8601形式以外の日時フォーマットでシリアライズしたい場合は、JsonConverter
を使ってカスタムフォーマットを指定します。
DateTime
専用のカスタムコンバーターを作成し、ToString
やParse
でフォーマットを制御します。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Globalization;
public class CustomDateTimeConverter : JsonConverter<DateTime>
{
private const string Format = "yyyy/MM/dd HH:mm:ss";
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string? value = reader.GetString();
if (value == null)
throw new JsonException("日時文字列がnullです。");
return DateTime.ParseExact(value, Format, CultureInfo.InvariantCulture);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
class Program
{
public class Event
{
[JsonConverter(typeof(CustomDateTimeConverter))]
public DateTime EventDate { get; set; }
}
static void Main()
{
var evt = new Event { EventDate = new DateTime(2024, 6, 1, 15, 30, 0) };
string jsonString = JsonSerializer.Serialize(evt);
Console.WriteLine(jsonString);
var deserialized = JsonSerializer.Deserialize<Event>(jsonString);
Console.WriteLine($"Deserialized Date: {deserialized?.EventDate}");
}
}
{"EventDate":"2024/06/01 15:30:00"}
Deserialized Date: 6/1/2024 3:30:00 PM
この例では、yyyy/MM/dd HH:mm:ss
という独自フォーマットで日時をシリアライズ・デシリアライズしています。
JsonConverter
属性を使ってEventDate
プロパティに適用しています。
カスタムフォーマットを使うことで、API仕様やユーザーインターフェースの要件に合わせた日時表現が可能になります。
ただし、フォーマットの指定ミスや不整合があるとデシリアライズ時に例外が発生するため、注意が必要です。
日時の扱いはシステム間のデータ連携で特に重要なポイントです。
標準のISO 8601形式を基本としつつ、必要に応じてカスタムフォーマットを使い分けることで、柔軟かつ安全な日時データのやり取りが実現できます。
エンコーディングとUTF-8
JSONのシリアライズ・デシリアライズにおいて、文字列のエンコーディングや特殊文字の扱いは重要なポイントです。
特にUTF-8が標準的に使われているため、その特徴や文字列のエスケープ処理、日本語の扱いについて理解しておく必要があります。
文字列エスケープ
JSONはテキストベースのデータフォーマットであり、文字列内に特定の制御文字や記号が含まれる場合はエスケープ処理が必要です。
System.Text.Json
は自動的に必要な文字をエスケープしてくれます。
主にエスケープされる文字は以下の通りです。
文字 | エスケープ表現 | 説明 |
---|---|---|
" (ダブルクォート) | \" | 文字列の区切り文字のため |
\ (バックスラッシュ) | \\ | エスケープ文字として使用 |
改行\n | \n | 改行コード |
復帰\r | \r | キャリッジリターン |
タブ\t | \t | タブ文字 |
バックスペース\b | \b | バックスペース |
フォームフィード\f | \f | フォームフィード |
例えば、文字列にダブルクォートや改行が含まれている場合、JSONでは以下のようにエスケープされます。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
string text = "Hello \"World\"\nNew line";
string jsonString = JsonSerializer.Serialize(text);
Console.WriteLine(jsonString);
}
}
"Hello \"World\"\nNew line"
このように、System.Text.Json
は自動的にエスケープ処理を行い、JSONの仕様に準拠した文字列を生成します。
エスケープされていない文字列をそのままJSONに含めると、パースエラーの原因になるため、必ず正しくエスケープされていることが重要です。
日本語の扱い
JSONはUTF-8エンコーディングを標準としているため、日本語などのマルチバイト文字も問題なく扱えます。
System.Text.Json
は内部的にUTF-8を使っているため、日本語の文字列をそのままシリアライズ・デシリアライズできます。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
string japaneseText = "こんにちは、世界!";
string jsonString = JsonSerializer.Serialize(japaneseText);
Console.WriteLine(jsonString);
string deserialized = JsonSerializer.Deserialize<string>(jsonString);
Console.WriteLine(deserialized);
}
}
"こんにちは、世界!"
こんにちは、世界!
この例では、日本語の文字列がエスケープされることなくそのままJSON文字列に含まれ、デシリアライズ後も正しく復元されています。
Unicodeエスケープとの違い
一部のJSONライブラリや設定では、日本語などの非ASCII文字を\uXXXX
形式のUnicodeエスケープに変換することがありますが、System.Text.Json
はデフォルトでUTF-8のまま出力します。
これにより、可読性が高く、ファイルサイズも抑えられます。
もしUnicodeエスケープに変換したい場合は、カスタムエンコーディングやコンバーターを実装する必要がありますが、通常はUTF-8のまま扱うことが推奨されます。
文字列のエスケープ処理とUTF-8による日本語の扱いは、JSONの正確なデータ交換に欠かせません。
System.Text.Json
はこれらを自動的に適切に処理するため、安心して日本語を含む文字列を扱えます。
非同期APIの利用
System.Text.Json
は非同期処理に対応したAPIを提供しており、大量データの読み書きやI/O操作と組み合わせて効率的にJSONのシリアライズ・デシリアライズが可能です。
ここでは、SerializeAsync
、DeserializeAsync
の使い方と、Stream
との連携について解説します。
SerializeAsync
SerializeAsync
は、オブジェクトを非同期に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()
{
Person person = new Person { Name = "Alice", Age = 30 };
using FileStream fs = new FileStream("person.json", FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true);
// 非同期でJSONを書き込む
await JsonSerializer.SerializeAsync(fs, person);
Console.WriteLine("JSONの書き込みが完了しました。");
}
}
この例では、FileStream
を非同期モードで開き、SerializeAsync
でPerson
オブジェクトをJSONとしてファイルに書き込んでいます。
await
を使うことで、書き込み完了まで非同期に待機します。
DeserializeAsync
DeserializeAsync
は、JSONデータを非同期にストリームから読み込み、指定した型のオブジェクトに変換するメソッドです。
大きな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()
{
using FileStream fs = new FileStream("person.json", FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true);
// 非同期でJSONを読み込み、Personオブジェクトに変換
Person person = await JsonSerializer.DeserializeAsync<Person>(fs);
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
このコードは、先ほど書き込んだperson.json
ファイルを非同期に読み込み、Person
オブジェクトにデシリアライズしています。
Streamとの組み合わせ
SerializeAsync
とDeserializeAsync
はStream
と組み合わせて使うことが前提となっています。
FileStream
以外にも、MemoryStream
やNetworkStream
、HttpContent
のストリームなど、さまざまなストリームに対応可能です。
メモリストリームを使った例
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()
{
Person person = new Person { Name = "Bob", Age = 40 };
using MemoryStream ms = new MemoryStream();
// メモリストリームに非同期でシリアライズ
await JsonSerializer.SerializeAsync(ms, person);
// ストリームの位置を先頭に戻す
ms.Position = 0;
// メモリストリームから非同期でデシリアライズ
Person deserialized = await JsonSerializer.DeserializeAsync<Person>(ms);
Console.WriteLine($"Name: {deserialized.Name}, Age: {deserialized.Age}");
}
}
Name: Bob, Age: 40
この例では、MemoryStream
を使ってメモリ上で非同期にシリアライズ・デシリアライズを行っています。
ファイルやネットワークに依存せず、テストや一時的なデータ処理に便利です。
非同期APIを活用することで、I/O待ちによるスレッドのブロックを防ぎ、アプリケーションの応答性やスループットを向上させられます。
特に大規模データやネットワーク通信を伴うJSON処理では、SerializeAsync
とDeserializeAsync
を積極的に利用しましょう。
高速化の考慮
JSONのシリアライズ・デシリアライズ処理を高速化するためには、System.Text.Json
のオプション設定やメモリ管理の工夫が重要です。
ここでは、パフォーマンス向上に役立つオプションやバッファ設定、さらにオブジェクトプールの活用方法について解説します。
オプションとバッファ設定
JsonSerializerOptions
にはパフォーマンスに影響を与える複数の設定項目があります。
適切に設定することで、処理速度やメモリ使用量を最適化できます。
DefaultBufferSize
DefaultBufferSize
は、シリアライズ・デシリアライズ時に内部で使用されるバッファのサイズを指定します。
デフォルトは16,384
(16KB)ですが、大きなJSONデータを扱う場合はバッファサイズを増やすことでI/O回数を減らし、パフォーマンスが向上することがあります。
var options = new JsonSerializerOptions
{
DefaultBufferSize = 32 * 1024 // 32KBに設定
};
ただし、バッファサイズを大きくしすぎるとメモリ消費が増えるため、適切なサイズを検証しながら調整してください。
IgnoreNullValues / DefaultIgnoreCondition
IgnoreNullValues
(.NET 5以前)やDefaultIgnoreCondition
(.NET 5以降)を使って、不要なnull
値のシリアライズを省略すると、出力データが小さくなり、処理時間の短縮につながります。
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
};
PropertyNameCaseInsensitive
デシリアライズ時にプロパティ名の大文字小文字を無視する設定ですが、true
にするとパフォーマンスが若干低下します。
必要な場合のみ有効にしましょう。
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
WriteIndented
インデント付きのJSONは可読性が高いですが、余計な空白や改行が増えるため、パフォーマンスとファイルサイズに影響します。
高速化を優先する場合はfalse
(デフォルト)に設定してください。
var options = new JsonSerializerOptions
{
WriteIndented = false
};
オブジェクトプール利用
大量のJSON処理を繰り返す場合、メモリの割り当てと解放がパフォーマンスのボトルネックになることがあります。
System.Text.Json
は内部でバッファやリソースを使うため、オブジェクトプールを活用して再利用することで高速化が可能です。
ArrayPool<byte>の活用
System.Buffers.ArrayPool<byte>
は、バイト配列の再利用を管理するプールです。
JsonSerializer
は内部的にこのプールを使ってバッファを確保しています。
自分でバッファを管理する場合は、ArrayPool<byte>.Shared
を利用してメモリ割り当てを減らせます。
using System.Buffers;
byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
try
{
// bufferを使った処理
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
カスタムバッファ管理とストリーム
大規模なJSON処理でストリームを使う場合、バッファの再利用が効果的です。
MemoryStream
やPipe
などと組み合わせて、バッファの割り当て回数を減らす設計が推奨されます。
オブジェクトプールによるUtf8JsonWriterの再利用
Utf8JsonWriter
はJSONの書き込みに使う構造体ですが、頻繁に生成・破棄するとパフォーマンスが低下します。
Utf8JsonWriter
をオブジェクトプールで管理し、再利用することで高速化が期待できます。
using System.Buffers;
using System.Text.Json;
var buffer = new ArrayBufferWriter<byte>();
var writer = new Utf8JsonWriter(buffer);
// JSON書き込み処理
writer.WriteStartObject();
writer.WriteString("Name", "Alice");
writer.WriteNumber("Age", 30);
writer.WriteEndObject();
writer.Flush();
string json = System.Text.Encoding.UTF8.GetString(buffer.WrittenSpan);
Console.WriteLine(json);
この例ではArrayBufferWriter<byte>
を使い、バッファを効率的に管理しています。
大量のJSON生成時に効果的です。
高速化を図る際は、まずはJsonSerializerOptions
の設定を見直し、不要な処理を省くことが基本です。
さらに、バッファやライターの再利用を意識した設計を行うことで、メモリ効率と処理速度の両面で大きな改善が見込めます。
パフォーマンス要件に応じてこれらの手法を組み合わせて活用してください。
オンラインAPI連携
C#でオンラインAPIとJSONデータをやり取りする際は、HttpClient
を使ったHTTP通信が基本です。
JSONのシリアライズ・デシリアライズを組み合わせて、効率的にデータ送受信を行う方法を解説します。
HttpClientでJSON送受信
HttpClient
はHTTPリクエストを送信し、レスポンスを受け取るためのクラスです。
JSONデータを送信する場合は、StringContent
やStreamContent
にJSON文字列をセットし、application/json
のContent-Typeを指定します。
レスポンスのJSONはJsonSerializer
でデシリアライズします。
using System;
using System.Net.Http;
using System.Text;
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()
{
var client = new HttpClient();
var person = new Person { Name = "Alice", Age = 30 };
string jsonRequest = JsonSerializer.Serialize(person);
var content = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
// POSTリクエストでJSONを送信
HttpResponseMessage response = await client.PostAsync("https://example.com/api/person", content);
response.EnsureSuccessStatusCode();
// レスポンスのJSONを文字列で取得
string jsonResponse = await response.Content.ReadAsStringAsync();
// JSONをPersonオブジェクトにデシリアライズ
Person responsePerson = JsonSerializer.Deserialize<Person>(jsonResponse);
Console.WriteLine($"Received: Name={responsePerson.Name}, Age={responsePerson.Age}");
}
}
Received: Name=Alice, Age=30
この例では、Person
オブジェクトをJSONに変換してPOSTリクエストのボディにセットし、APIからのJSONレスポンスを受け取って再びPerson
オブジェクトに変換しています。
変換を挟んだリクエストフロー
実際のAPI連携では、送受信するJSONの構造がC#のクラスと完全に一致しないことも多いため、変換処理を挟むことが一般的です。
DTO(Data Transfer Object)を用意してAPI仕様に合わせたクラスを作成し、内部のビジネスモデルとマッピングする方法がよく使われます。
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
// ビジネスモデル
public class User
{
public string FullName { get; set; }
public int Age { get; set; }
}
// API用DTO
public class UserDto
{
public string name { get; set; }
public int age { get; set; }
}
class Program
{
static async Task Main()
{
var client = new HttpClient();
// ビジネスモデルのインスタンス
User user = new User { FullName = "Bob Smith", Age = 40 };
// DTOに変換(マッピング)
UserDto dto = new UserDto
{
name = user.FullName,
age = user.Age
};
string jsonRequest = JsonSerializer.Serialize(dto);
var content = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync("https://example.com/api/user", content);
response.EnsureSuccessStatusCode();
string jsonResponse = await response.Content.ReadAsStringAsync();
// APIレスポンスをDTOで受け取る
UserDto responseDto = JsonSerializer.Deserialize<UserDto>(jsonResponse);
// DTOからビジネスモデルに変換
User responseUser = new User
{
FullName = responseDto.name,
Age = responseDto.age
};
Console.WriteLine($"Received User: FullName={responseUser.FullName}, Age={responseUser.Age}");
}
}
Received User: FullName=Bob Smith, Age=40
この例では、API仕様に合わせてUserDto
を用意し、ビジネスモデルのUser
と相互に変換しています。
これにより、APIのJSON構造の変更に柔軟に対応でき、内部ロジックを保守しやすくなります。
HttpClient
とSystem.Text.Json
を組み合わせることで、JSONの送受信をシンプルかつ効率的に実装できます。
変換処理を適切に挟むことで、API仕様の違いを吸収し、堅牢な連携を実現しましょう。
動的型とJsonDocument
JSONデータの構造が事前に固定されていない場合や、柔軟にJSONを解析・操作したい場合には、System.Text.Json
のJsonDocument
とJsonElement
を使う方法が有効です。
これらは動的にJSONを読み取り、部分的にアクセス・操作できるAPIを提供します。
JsonDocument読み取り
JsonDocument
は、JSON文字列やバイト配列から読み取ったJSONデータをメモリ上に展開し、ツリー構造として扱います。
JsonDocument.Parse
メソッドでJSONをパースし、JsonDocument
オブジェクトを取得します。
JsonDocument
はIDisposable
を実装しているため、使用後は必ずDispose
するかusing
文で囲む必要があります。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
string jsonString = @"{
""Name"": ""Alice"",
""Age"": 30,
""Skills"": [""C#"", ""JSON"", ""ASP.NET""]
}";
using JsonDocument doc = JsonDocument.Parse(jsonString);
JsonElement root = doc.RootElement;
Console.WriteLine($"Name: {root.GetProperty("Name").GetString()}");
Console.WriteLine($"Age: {root.GetProperty("Age").GetInt32()}");
}
}
Name: Alice
Age: 30
この例では、JsonDocument.Parse
でJSON文字列をパースし、RootElement
からトップレベルのJSONオブジェクトを取得しています。
GetProperty
メソッドで特定のプロパティにアクセスし、型に応じたGetString
やGetInt32
で値を取得しています。
JsonElement操作
JsonElement
はJsonDocument
内のJSONノードを表し、オブジェクト、配列、値などの種類に応じて操作できます。
動的にJSONの構造を調べたり、値を取得したりする際に使います。
プロパティの存在チェック
存在しないプロパティにアクセスすると例外が発生するため、TryGetProperty
で安全にチェックできます。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
string jsonString = @"{ ""Name"": ""Bob"" }";
using JsonDocument doc = JsonDocument.Parse(jsonString);
JsonElement root = doc.RootElement;
if (root.TryGetProperty("Age", out JsonElement ageElement))
{
Console.WriteLine($"Age: {ageElement.GetInt32()}");
}
else
{
Console.WriteLine("Ageプロパティは存在しません。");
}
}
}
Ageプロパティは存在しません。
配列の列挙
配列要素はEnumerateArray
メソッドで列挙可能です。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
string jsonString = @"{ ""Skills"": [""C#"", ""JSON"", ""ASP.NET""] }";
using JsonDocument doc = JsonDocument.Parse(jsonString);
JsonElement root = doc.RootElement;
JsonElement skills = root.GetProperty("Skills");
foreach (JsonElement skill in skills.EnumerateArray())
{
Console.WriteLine(skill.GetString());
}
}
}
C#
JSON
ASP.NET
型の判定
JsonElement
はValueKind
プロパティでノードの種類を判定できます。
例えば、オブジェクト、配列、文字列、数値、真偽値などを判別可能です。
using System;
using System.Text.Json;
class Program
{
static void Main()
{
string jsonString = @"{ ""Value"": 123 }";
using JsonDocument doc = JsonDocument.Parse(jsonString);
JsonElement root = doc.RootElement;
JsonElement value = root.GetProperty("Value");
switch (value.ValueKind)
{
case JsonValueKind.Number:
Console.WriteLine($"数値: {value.GetInt32()}");
break;
case JsonValueKind.String:
Console.WriteLine($"文字列: {value.GetString()}");
break;
default:
Console.WriteLine("その他の型");
break;
}
}
}
数値: 123
JsonDocument
とJsonElement
を使うことで、事前に型を定義しなくても柔軟にJSONを解析・操作できます。
APIレスポンスの一部だけを扱いたい場合や、動的に構造が変わるJSONを処理する際に特に有効です。
メモリ管理に注意しつつ、必要な部分だけを効率的に読み取ることが可能です。
System.Text.JsonとNewtonsoft.Jsonの比較
C#でJSON処理を行う際に代表的なライブラリであるSystem.Text.Json
とNewtonsoft.Json
(Json.NET)には、それぞれ特徴や機能の違いがあります。
ここでは両者の主な機能差を一覧で示し、Newtonsoft.Json
からSystem.Text.Json
へ移行する際のポイントを解説します。
機能差一覧
機能・特徴 | System.Text.Json | Newtonsoft.Json (Json.NET) |
---|---|---|
標準搭載 | .NET Core 3.0以降、.NET 5以降に標準搭載 | 外部パッケージ(NuGet)として提供 |
パフォーマンス | 高速で軽量 | 比較的遅いが機能豊富 |
非同期API | 充実(SerializeAsync/DeserializeAsync) | 限定的 |
動的JSON操作 | JsonDocument /JsonElement で対応 | JObject /JToken で柔軟に操作可能 |
カスタムコンバーター | サポートありJsonConverter<T> | サポートありJsonConverter |
属性による制御 | JsonPropertyName 、JsonIgnore など | JsonProperty 、JsonIgnore など |
循環参照の扱い | デフォルトで例外をスロー | 循環参照を許可する設定が可能 |
スキーマ検証 | 非対応 | サードパーティ製プラグインで対応可能 |
LINQ to JSON | 非対応 | 強力なLINQ操作が可能 |
カスタムシリアライザー | サポートあり | サポートあり |
互換性 | .NET Core以降に最適化 | .NET Frameworkから最新まで対応 |
JSONPathサポート | なし | あり(サードパーティライブラリで対応) |
設定の柔軟性 | 限定的 | 非常に柔軟 |
コミュニティとドキュメント | Microsoft公式サポート | 大規模なコミュニティと豊富なドキュメント |
移行時のポイント
Newtonsoft.Json
からSystem.Text.Json
へ移行する際には、以下の点に注意が必要です。
機能の差異を把握する
System.Text.Json
は高速で軽量ですが、Newtonsoft.Json
に比べて機能が限定的です。
特に以下の機能が不足している場合があります。
- LINQ to JSON
(JObject
やJToken)
のような動的操作 - JSONスキーマ検証
- JSONPathのサポート
- 一部のカスタムシリアライザーや複雑な変換ロジック
これらの機能が必要な場合は、移行を慎重に検討してください。
属性名や設定の違いに注意
属性名や設定オプションが異なるため、クラスの属性を置き換える必要があります。
Newtonsoft.Json属性 | System.Text.Json属性 |
---|---|
[JsonProperty("name")] | [JsonPropertyName("name")] |
[JsonIgnore] | [JsonIgnore] |
[JsonConverter(typeof(...))] | [JsonConverter(typeof(...))] |
また、System.Text.Json
はデフォルトでプロパティ名の大文字小文字を区別するため、PropertyNameCaseInsensitive
オプションの設定が必要な場合があります。
循環参照の扱い
Newtonsoft.Json
は循環参照を許可する設定が可能ですが、System.Text.Json
はデフォルトで例外をスローします。
循環参照がある場合は、設計の見直しやカスタムコンバーターの実装が必要です。
カスタムコンバーターの書き換え
Newtonsoft.Json
のカスタムコンバーターはJsonConverter
を継承しますが、System.Text.Json
ではジェネリックのJsonConverter<T>
を使います。
APIが異なるため、コンバーターの書き換えが必要です。
非同期APIの活用
System.Text.Json
は非同期APIが充実しているため、ファイルやネットワークI/Oと組み合わせて効率的に処理できます。
移行時に非同期処理を導入する良い機会です。
例外処理の違い
例外の種類やメッセージが異なるため、エラーハンドリングの見直しが必要です。
特にデシリアライズ時の型不一致や不正JSONの検出方法に注意してください。
依存関係の整理
Newtonsoft.Json
は外部パッケージなので、移行後は不要なNuGetパッケージを削除し、プロジェクトの依存関係を軽量化できます。
これらのポイントを踏まえ、段階的に移行を進めることが推奨されます。
まずは小規模なモジュールや新規開発部分からSystem.Text.Json
を導入し、問題がなければ徐々に範囲を広げる方法が安全です。
両者の特徴を理解し、プロジェクトの要件に最適なライブラリを選択してください。
トラブルシューティング
JSONのシリアライズ・デシリアライズを行う際に遭遇しやすい問題とその対処法を解説します。
特に循環参照、型不一致、大文字小文字の相違、暗黙型変換エラーに焦点を当てます。
循環参照問題
オブジェクトのプロパティが相互に参照し合う「循環参照」がある場合、System.Text.Json
のデフォルト設定ではシリアライズ時に例外が発生します。
これは無限ループを防ぐための仕様です。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Node
{
public string Name { get; set; }
public Node Next { get; set; }
}
class Program
{
static void Main()
{
var node1 = new Node { Name = "Node1" };
var node2 = new Node { Name = "Node2", Next = node1 };
node1.Next = node2; // 循環参照
try
{
string json = JsonSerializer.Serialize(node1);
Console.WriteLine(json);
}
catch (JsonException ex)
{
Console.WriteLine($"循環参照エラー: {ex.Message}");
}
}
}
循環参照エラー: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 64. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. Path: $.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Next.Name.
対策
ReferenceHandler.Preserve
を使う
.NET 5以降で利用可能なReferenceHandler.Preserve
をJsonSerializerOptions
に設定すると、循環参照を検出し、参照IDを使ってシリアライズできます。
var options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve,
WriteIndented = true
};
string json = JsonSerializer.Serialize(node1, options);
Console.WriteLine(json);
- 設計の見直し
循環参照が不要な場合は、データ構造を見直して参照を切るか、[JsonIgnore]
属性で一方の参照を除外する方法もあります。
型不一致エラー
JSONの値とC#のプロパティ型が一致しない場合、デシリアライズ時に例外が発生します。
例えば、数値型のプロパティに文字列が入っている場合などです。
using System;
using System.Text.Json;
public class Person
{
public int Age { get; set; }
}
class Program
{
static void Main()
{
string json = "{\"Age\":\"thirty\"}";
try
{
Person person = JsonSerializer.Deserialize<Person>(json);
}
catch (JsonException ex)
{
Console.WriteLine($"型不一致エラー: {ex.Message}");
}
}
}
型不一致エラー: The JSON value could not be converted to System.Int32. Path: $.Age | LineNumber: 0 | BytePositionInLine: 15.
対策
- JSONのデータ型を正しく修正します
- カスタムコンバーターを作成して柔軟に変換処理を行います
- プロパティの型を文字列に変更し、後で変換処理を行います
大文字小文字の相違
System.Text.Json
はデフォルトでプロパティ名の大文字小文字を区別します。
JSONのキー名とC#のプロパティ名の大文字小文字が異なると、デシリアライズ時にマッピングされず、プロパティが初期値のままになることがあります。
using System;
using System.Text.Json;
public class Person
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
string json = "{\"name\":\"Alice\"}"; // 小文字の"name"
Person person = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine($"Name: {person.Name ?? "null"}"); // nullになる
}
}
Name: null
対策
JsonSerializerOptions
のPropertyNameCaseInsensitive
をtrue
に設定します
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
Person person = JsonSerializer.Deserialize<Person>(json, options);
Console.WriteLine($"Name: {person.Name}");
Name: Alice
- JSONのキー名をC#のプロパティ名に合わせます
[JsonPropertyName]
属性で明示的にマッピングします
暗黙型変換エラー
JSONの値をC#の型に変換する際、暗黙的な型変換ができない場合にエラーが発生します。
例えば、int
型のプロパティにdouble
型の値が入っている場合などです。
using System;
using System.Text.Json;
public class Data
{
public int Number { get; set; }
}
class Program
{
static void Main()
{
string json = "{\"Number\": 12.34}"; // double値
try
{
Data data = JsonSerializer.Deserialize<Data>(json);
}
catch (JsonException ex)
{
Console.WriteLine($"暗黙型変換エラー: {ex.Message}");
}
}
}
暗黙型変換エラー: The JSON value could not be converted to System.Int32. Path: $.Number | LineNumber: 0 | BytePositionInLine: 16.
対策
- JSONの値をC#の型に合わせて修正する(例:整数にする)
- プロパティの型を
double
やdecimal
など適切な型に変更します - カスタムコンバーターを作成し、柔軟に変換処理を行います
これらのトラブルは、JSONとC#の型や構造の不整合が原因で起こりやすいです。
エラーメッセージをよく確認し、適切な設定やコード修正を行うことで解決できます。
セキュリティ考慮
JSONデータを扱う際には、不正入力や過大なデータによる攻撃を防ぐためのセキュリティ対策が欠かせません。
ここでは、不正入力対策、データサイズの検証、そしてストリーミング読み込みを活用した防御方法について解説します。
不正入力対策
JSONの入力が外部から提供される場合、悪意のあるデータや不正な形式のJSONが送られてくる可能性があります。
これにより、アプリケーションの異常終了や情報漏洩、サービス拒否(DoS)攻撃のリスクが高まります。
対策例
- 入力のバリデーション
受信したJSON文字列の構文チェックを行い、JsonSerializer.Deserialize
やJsonDocument.Parse
で例外が発生しないか確認します。
例外が発生した場合は処理を中断し、適切なエラーメッセージを返します。
- ホワイトリスト方式の検証
期待するJSON構造やプロパティ名、値の型を明確に定義し、それ以外のデータは拒否する設計にします。
これにより、予期しないデータの混入を防げます。
- カスタムコンバーターでの検証
特定の型に対してカスタムコンバーターを実装し、値の範囲や形式を厳密にチェックすることも有効です。
- 例外処理の徹底
デシリアライズ時に発生するJsonException
などの例外を適切にキャッチし、システムの異常終了を防ぎます。
データサイズ検証
大量のJSONデータを受け取ると、メモリ不足や処理遅延を引き起こし、サービスの可用性に影響を与える恐れがあります。
特に外部からの入力では、意図的に巨大なデータを送信されるリスクがあります。
対策例
- 受信データのサイズ制限
HTTPリクエストのボディサイズをサーバー側で制限することが基本です。
ASP.NET Coreなどのフレームワークでは、MaxRequestBodySize
などの設定で制御可能です。
- JSON文字列の長さチェック
デシリアライズ前に文字列の長さをチェックし、閾値を超える場合は処理を中止します。
- ストリーミング処理の活用
大きなJSONを一括で読み込まず、ストリームを使って部分的に処理することでメモリ消費を抑制します(後述)。
- プロパティ数や配列要素数の制限
JSONの構造を解析し、許容範囲を超えるプロパティ数や配列の要素数があれば拒否するロジックを組み込みます。
ストリーミング読み込みによる防御
System.Text.Json
のJsonDocument
やJsonSerializer.DeserializeAsync
はストリーミング対応しており、大きなJSONデータを一度にメモリに読み込まずに処理できます。
これにより、メモリ消費を抑えつつ、不正な巨大データによる攻撃を緩和できます。
ストリーミングの利点
- メモリ効率の向上
データをチャンク単位で読み込むため、巨大なJSONでもメモリ不足を防げます。
- 早期検出
不正なJSON構造やサイズ超過を途中で検出し、処理を中断できます。
- 応答性の改善
データの一部を処理しながら次のデータを読み込むため、全体の処理時間を短縮できます。
実装例
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using FileStream fs = new FileStream("large.json", FileMode.Open, FileAccess.Read);
var options = new JsonSerializerOptions
{
// 必要に応じてオプションを設定
};
try
{
var document = await JsonDocument.ParseAsync(fs);
// JSONの内容を部分的に検証・処理
if (document.RootElement.GetArrayLength() > 1000)
{
Console.WriteLine("配列の要素数が多すぎます。処理を中止します。");
return;
}
// 続けて処理
}
catch (JsonException ex)
{
Console.WriteLine($"不正なJSONです: {ex.Message}");
}
}
}
これらのセキュリティ対策を組み合わせることで、外部からの不正なJSON入力や過大なデータによる攻撃を防ぎ、安全かつ安定したJSON処理を実現できます。
特にWeb APIなど公開環境では必須の対策です。
バージョン別の新機能
System.Text.Json
は.NETのバージョンアップに伴い、機能強化や改善が継続的に行われています。
ここでは、特に.NET 6および.NET 7で追加された主な新機能を紹介します。
.NET 6で追加された項目
.NET 6では、System.Text.Json
に対して以下のような重要な機能追加や改善が行われました。
JsonSerializerContextによるソースジェネレーション対応
- 概要
JSONシリアライズ・デシリアライズのコードをコンパイル時に生成する「ソースジェネレーション」機能が導入されました。
これにより、リフレクションを使わずに高速かつ低メモリでの処理が可能になります。
- メリット
- 起動時間の短縮
- メモリ使用量の削減
- 実行時のパフォーマンス向上
- 使い方の例
using System.Text.Json.Serialization;
[JsonSerializable(typeof(Person))]
public partial class PersonJsonContext : JsonSerializerContext
{
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// シリアライズ・デシリアライズ時にコンテキストを指定
string json = JsonSerializer.Serialize(person, PersonJsonContext.Default.Person);
Person p = JsonSerializer.Deserialize<Person>(json, PersonJsonContext.Default.Person);
JsonNode APIの追加
- 概要
動的なJSON操作を可能にするJsonNode
、JsonObject
、JsonArray
などのAPIが追加されました。
これにより、Newtonsoft.Json
のJObject
のような柔軟な操作が可能になりました。
- 特徴
- 動的にJSONツリーを構築・編集できる
- 型安全なAPIと動的APIの両方を提供
ReferenceHandler.Preserveの改善
- 循環参照のサポートが強化され、より安定した動作が可能になりました
パフォーマンスの向上
- シリアライズ・デシリアライズの内部処理が最適化され、特に大規模データの処理速度が改善されました
.NET 7で追加された項目
.NET 7では、さらに多くの機能強化と利便性向上が図られています。
JsonSerializerOptionsの拡張
NumberHandling
オプションの追加
数値のシリアライズ・デシリアライズ時の挙動を細かく制御可能になりました。
例えば、文字列として数値を扱う、浮動小数点数の丸めなどが設定できます。
var options = new JsonSerializerOptions
{
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString
};
JsonEncodedTextの最適化
- JSONプロパティ名のエンコード処理が高速化され、繰り返し使う文字列のパフォーマンスが向上しました
JsonSerializerの非同期API強化
- 非同期シリアライズ・デシリアライズのパフォーマンスが改善され、より効率的なI/O処理が可能になりました
JsonPathの部分的サポート(プレビュー)
- JSONの特定のパスにアクセスする機能がプレビューとして導入され、動的なJSON操作の利便性が向上しています
JsonSerializerContextの機能拡張
- ソースジェネレーションのサポート範囲が拡大し、より多様な型やシナリオに対応可能になりました
.NET 6および.NET 7の新機能は、System.Text.Json
のパフォーマンスと柔軟性を大幅に向上させています。
最新の.NET環境を利用する場合は、これらの機能を積極的に活用することで、より高速で堅牢なJSON処理が実現できます。
サンプルコード集
ここでは、System.Text.Json
を使ったJSONのシリアライズ・デシリアライズに関する代表的なサンプルコードを紹介します。
基本的な使い方から属性を使った変換、カスタムコンバーターの実装例、さらにHTTP通信でのJSON送受信例まで幅広くカバーします。
基本シリアライズ例
C#のオブジェクトを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()
{
Person person = new Person { Name = "Alice", Age = 30 };
// オブジェクトをJSON文字列にシリアライズ
string jsonString = JsonSerializer.Serialize(person);
Console.WriteLine(jsonString);
}
}
{"Name":"Alice","Age":30}
このコードは、Person
オブジェクトをJSON形式の文字列に変換し、コンソールに出力しています。
属性付き変換例
JsonPropertyName
属性を使って、C#のプロパティ名とJSONのキー名をカスタマイズする例です。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Person
{
[JsonPropertyName("full_name")]
public string Name { get; set; }
[JsonPropertyName("age_in_years")]
public int Age { get; set; }
}
class Program
{
static void Main()
{
Person person = new Person { Name = "Bob", Age = 25 };
string jsonString = JsonSerializer.Serialize(person);
Console.WriteLine(jsonString);
}
}
{"full_name":"Bob","age_in_years":25}
この例では、Name
プロパティがfull_name
、Age
プロパティがage_in_years
としてJSONに変換されています。
カスタムコンバーター例
独自の変換ロジックを実装したカスタムコンバーターの例です。
ここでは、温度を表すTemperature
クラスを文字列形式(例:”25C”)でシリアライズ・デシリアライズします。
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Temperature
{
public int Degrees { get; set; }
public bool IsCelsius { get; set; }
public override string ToString() => $"{Degrees}{(IsCelsius ? "C" : "F")}";
}
public class TemperatureConverter : JsonConverter<Temperature>
{
public override Temperature Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string value = reader.GetString();
int degrees = int.Parse(value.Substring(0, value.Length - 1));
bool isCelsius = value.EndsWith("C");
return new Temperature { Degrees = degrees, IsCelsius = isCelsius };
}
public override void Write(Utf8JsonWriter writer, Temperature value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
public class WeatherForecast
{
public DateTime Date { get; set; }
[JsonConverter(typeof(TemperatureConverter))]
public Temperature Temperature { get; set; }
}
class Program
{
static void Main()
{
WeatherForecast forecast = new WeatherForecast
{
Date = DateTime.Now,
Temperature = new Temperature { Degrees = 25, IsCelsius = true }
};
string jsonString = JsonSerializer.Serialize(forecast);
Console.WriteLine(jsonString);
WeatherForecast deserialized = JsonSerializer.Deserialize<WeatherForecast>(jsonString);
Console.WriteLine($"Date: {deserialized.Date}, Temperature: {deserialized.Temperature}");
}
}
{"Date":"2025-05-16T10:00:10.6160272+09:00","Temperature":"25C"}
Date: 2025/05/16 10:00:10, Temperature: 25C
このコードでは、Temperature
型に対してカスタムコンバーターを適用し、文字列形式でのJSON変換を実現しています。
HTTP通信例
HttpClient
を使ってJSONデータを送受信する例です。
POSTリクエストでJSONを送信し、レスポンスのJSONをデシリアライズしています。
using System;
using System.Net.Http;
using System.Text;
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()
{
var client = new HttpClient();
var person = new Person { Name = "Charlie", Age = 28 };
string jsonRequest = JsonSerializer.Serialize(person);
var content = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync("https://example.com/api/person", content);
response.EnsureSuccessStatusCode();
string jsonResponse = await response.Content.ReadAsStringAsync();
Person responsePerson = JsonSerializer.Deserialize<Person>(jsonResponse);
Console.WriteLine($"Received: Name={responsePerson.Name}, Age={responsePerson.Age}");
}
}
Received: Name=Charlie, Age=28
この例では、Person
オブジェクトをJSONに変換してAPIに送信し、APIからのJSONレスポンスを受け取って再びPerson
オブジェクトに変換しています。
これらのサンプルコードを参考に、System.Text.Json
を使ったJSON処理を効率的に実装してください。
Q1: System.Text.JsonとNewtonsoft.Jsonのどちらを使うべきですか?
用途やプロジェクトの要件によります。
System.Text.Json
は.NET標準で高速かつ軽量ですが、機能は限定的です。
動的JSON操作や高度なカスタマイズが必要な場合はNewtonsoft.Json
が適しています。
新規プロジェクトやパフォーマンス重視ならSystem.Text.Json
を推奨します。
Q2: JSONのプロパティ名を小文字にしたいのですが、どうすればよいですか?
JsonSerializerOptions
のPropertyNamingPolicy
にJsonNamingPolicy.CamelCase
を設定すると、C#のプロパティ名をキャメルケース(先頭小文字)に変換してシリアライズできます。
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
Q3: JSONのデシリアライズ時にプロパティ名の大文字小文字を無視したいです。
JsonSerializerOptions
のPropertyNameCaseInsensitive
をtrue
に設定してください。
これにより、JSONのキー名とC#のプロパティ名の大文字小文字の違いを無視してマッピングできます。
Q4: 循環参照があるオブジェクトをシリアライズするとエラーになります。どうすればよいですか?
JsonSerializerOptions
のReferenceHandler
にReferenceHandler.Preserve
を設定すると、循環参照を検出して参照IDを使ったシリアライズが可能です。
var options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve
};
Q5: JSONの一部だけを動的に読み取りたい場合はどうすればよいですか?
JsonDocument
とJsonElement
を使うと、型を事前に定義せずにJSONの特定の部分を動的に読み取れます。
JsonDocument.Parse
でJSONをパースし、GetProperty
やEnumerateArray
で必要な部分にアクセスします。
Q6: カスタムコンバーターはどのように作成しますか?
JsonConverter<T>
を継承したクラスを作成し、Read
メソッドでデシリアライズ処理、Write
メソッドでシリアライズ処理を実装します。
対象のプロパティに[JsonConverter(typeof(YourConverter))]
属性を付けるか、JsonSerializerOptions.Converters
に登録して適用します。
Q7: 非同期でJSONを読み書きするにはどうすればよいですか?
JsonSerializer
のSerializeAsync
とDeserializeAsync
メソッドを使います。
これらはStream
と組み合わせて使用し、ファイルやネットワーク通信での非同期処理に適しています。
Q8: JSONのシリアライズ結果にインデントを付けるには?
JsonSerializerOptions
のWriteIndented
をtrue
に設定すると、改行やスペースを含む見やすいJSONが生成されます。
Q9: JSONのプロパティをシリアライズから除外したい場合は?
[JsonIgnore]
属性をプロパティに付けると、そのプロパティはシリアライズ・デシリアライズの対象から除外されます。
Q10: 日本語を含むJSONは正しく扱えますか?
System.Text.Json
はUTF-8を標準としているため、日本語などのマルチバイト文字も問題なくシリアライズ・デシリアライズできます。
特別な設定は不要です。
これらのFAQを参考に、System.Text.Json
の利用時によくある疑問や問題を解決してください。
まとめ
この記事では、C#でJSON文字列を高速かつ効率的に変換するためのSystem.Text.Json
の基本から応用までを解説しました。
ライブラリの選択肢やシリアライズ・デシリアライズの手順、属性による制御、カスタムコンバーターの作成方法、非同期処理やパフォーマンス最適化、API連携、トラブル対処法、セキュリティ対策まで幅広く理解できます。
これにより、実務でのJSON処理を安全かつ高速に実装できる知識が身につきます。