はじめに
今回は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); }