はじめに
今回はSystem.Text.Jsonを用いてSchemaが変わる可能性があるようなJsonをデシリアライズする方法について紹介したいと思います。
↓利用される場面
- 逆シリアル化する型がない
- 受信した JSON に固定スキーマがなく、含まれている内容を確認するために検査する必要がある。
System.Text.Json で JSON DOM を使用する方法 - .NET | Microsoft Learn
概要
Schemaから判断して逆シリアル化できない場合は以下の2つの手法をとり JSON ドキュメント オブジェクト モデル(DOM)を構築する必要があります。
JsonDocumentとJsonElementを利用するJsonNode及びその派生クラスを利用する
今回はJsonDocumentとJsonElementを利用した例を紹介したいと思います。
JsonDocument を使用すると、Utf8JsonReader を使用して読み取り専用 DOM を構築することができます。 ペイロードを構成する JSON 要素には、JsonElement 型を使用してアクセスできます。 JsonElement 型では、配列とオブジェクト列挙子と共に、JSON テキストを一般的な .NET 型に変換する API が提供されます。 JsonDocument では RootElement プロパティが公開されます。
System.Text.Json で JSON DOM を使用する方法 - .NET | Microsoft Learn
やり方
JsonDocumentの利用
JSON文字列もしくはUTF-8のバイト配列からJsonDocumentを生成します。
// 文字列から string json = JsonSerializer.Serialize(target); using JsonDocument document = JsonDocument.Parse(json); // UTF-8のバイト配列から Memory<byte> json = JsonSerializer.SerializeToUtf8Bytes(target); using JsonDocument document = JsonDocument.Parse(json);
Parse内部で扱うのはUTF-8なので、可能であればstringは利用しない方が効率がいいはずです。
またJsonDocumentはIDisposableを実装しているので、usingを忘れないでおきましょう。(もしくは直でDisposeを叩くか)
JsonElementの利用
Parseができたらデータを取り出します。例えば以下のようなJsonが入力として与えられた場合を考えます。
↓入力されるJson
{ "Age": 40, "Name": "John" }
これのAgeとNameを取り出してみます。
using JsonDocument doc = JsonDocument.Parse(data); int age = doc.RootElement .GetProperty("Age") .GetInt32(); string? name = doc.RootElement .GetProperty("Name") .GetString(); Console.WriteLine(age); // 40 Console.WriteLine(name); // John
JsonElement.GetPropertyによってAgeもしくはNameに対応するJsonElementを取得し、GetInt32()・GetStringで値を取り出します。
見つからない時はエラーになる
ただ注意点として引数のpropertyNameに対応するプロパティが見つからない場合はエラーが出力されてしまいます。
Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
エラーにしないためにはJsonElement.TryGetProperty・JsonElement.TryGetInt32を利用します。
using JsonDocument doc = JsonDocument.Parse(data); JsonElement root = doc.RootElement; if (root.TryGetProperty("Age", out JsonElement ageJsonElement)) { if (ageJsonElement.TryGetInt32(out int age)) { // 40 Console.WriteLine(age); } }
配列が列挙する
配列が含まれている場合はJsonElement.EnumerateArrayを利用します。
↓入力されるJson
{ "Class Name": "Science", "Students": [ { "Name": "John", "Grade": 94.3 }, { "Name": "James", "Grade": 81.0 }, { "Name": "Julia", "Grade": 91.9 }, { "Name": "Jessica", "Grade": 72.4 }, { "Name": "Johnathan" } ] }
double sum = 0d; using JsonDocument doc = JsonDocument.Parse(x); JsonElement root = doc.RootElement; JsonElement studentsElement = root.GetProperty("Students"); foreach (JsonElement student in studentsElement.EnumerateArray()) { if(student.TryGetProperty("Grade", out JsonElement gradeElement)) { sum += gradeElement.GetDouble(); } } // 67.92 Console.WriteLine($"Average : {sum / studentsElement.GetArrayLength()}");
プロパティを列挙する
プロパティを列挙するためにはJsonElement.EnumerateObjectを利用します。
↓入力されるJson
{ "Class Name": "Science", "Students": [ { "Name": "John", "Grade": 94.3 }, { "Name": "James", "Grade": 81.0 }, { "Name": "Julia", "Grade": 91.9 }, { "Name": "Jessica", "Grade": 72.4 }, { "Name": "Johnathan" } ] }
using JsonDocument doc = JsonDocument.Parse(x); JsonElement root = doc.RootElement; foreach (JsonProperty jsonProperty in root.EnumerateObject()) { // ClassName, Students Console.WriteLine(jsonProperty.Name); }