未知の Json を扱う一連の記事をまとめた記事を書きました。
こちらの記事で一気読みできます。
■ 現行世代の Json API / System.Text.Json
Json を扱う際には .NET Framework 時代には Json.NET というライブラリがよく使われていました。今の時代 (.NET Core 3 以降) は System.Text.Json
を使用します。
今回は System.Text.Json
の
System.Text.Json.JsonDocument.Parse
メソッドによるSystem.Text.Json.JsonDocument
で扱うTValue System.Text.Json.JsonSerializer.Deserialize<TValue>
メソッドでSystem.Dynamic.ExpandoObject
を扱う
ことで未知の Json を扱うことができました。
.NET Framework 時代で未知の Json を扱う記事を以前に書きました。
今回は同じようなことを System.Text.Json
で試していきます。
■ System.Text.Json.JsonDocument
メソッドの定義は次のようになっています。
public static JsonDocument Parse(string json, JsonDocumentOptions options = default)
とりあえず以前の記事のように、dynamic
型変数に代入して使ってみましょう。
dynamic の中身
using dynamic document = System.Text.Json.JsonDocument.Parse( @"{ ""prop1"": ""value1"", ""prop2"": ""value2""}"); Console.WriteLine(((object)document).GetType().FullName); // System.Text.Json.JsonDocument
中身の型は当たり前ですが、System.Text.Json.JsonDocument
です。
プロパティ
プロパティを取り出そうとしてみましょう
using dynamic document = System.Text.Json.JsonDocument.Parse( @"{ ""prop1"": ""value1"", ""prop2"": ""value2""}"); // 実行時エラー // Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: ''System.Text.Json.JsonDocument' does not contain a definition for 'prop1'' Console.WriteLine(document.prop1); Console.WriteLine(document.prop2);
残念ながら、実行時に例外が発生します。
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: ''System.Text.Json.JsonDocument' does not contain a definition for 'prop1''
プロパティは次のようにして取り出せます。
System.Text.Json.JsonDocument document = System.Text.Json.JsonDocument.Parse( @"{ ""prop1"": ""value1"", ""prop2"": ""value2""}"); Console.WriteLine(document.RootElement.GetProperty("prop1").GetRawText()); // "value1" と出力される Console.WriteLine(document.RootElement.GetProperty("prop2").GetRawText()); // "value2" と出力される Console.WriteLine(document.RootElement.GetProperty("prop1").GetString()); // value1 と出力される Console.WriteLine(document.RootElement.GetProperty("prop2").GetString()); // value2 と出力される
■ System.Text.Json.JsonSerializer.Deserialize
最初に結論を出しておくと、Json に階層化された構造があると、嬉しい結果になりません。その詳細は後述します。
メソッドの定義は次のようになっています。
public static TValue Deserialize<TValue>(string json, JsonSerializerOptions options = null);
とりあえず以前の記事のように、dynamic
型変数に代入して使ってみましょう。
dynamic の中身
もう敢えて確認する必要もないですが、System.Dynamic.ExpandoObject
です。
dynamic document = System.Text.Json.JsonSerializer.Deserialize<System.Dynamic.ExpandoObject>( @"{ ""prop1"": ""value1"", ""prop2"": ""value2""}"); Console.WriteLine(document.GetType().FullName); // System.Dynamic.ExpandoObject
プロパティを表示する
普通に dynamic っぽく。
dynamic document = System.Text.Json.JsonSerializer.Deserialize<System.Dynamic.ExpandoObject>( @"{ ""prop1"": ""value1"", ""prop2"": ""value2""}"); Console.WriteLine(document.prop1); // value1 と出力される Console.WriteLine(document.prop2); // value2 と出力される
System.Dynamic.ExpandoObject
は IDictionary<string, object>
を実装しているので、Key と Value を foreach
で回せます。
dynamic document = System.Text.Json.JsonSerializer.Deserialize<System.Dynamic.ExpandoObject>( @"{ ""prop1"": ""value1"", ""prop2"": ""value2""}"); foreach (var m in document) Console.WriteLine($"key:{m.Key}, Value:{m.Value}"); // key:prop1, Value:value1 // key:prop2, Value:value2 // と出力される
プロパティを追加する
IDictionary
の Add
メソッドでプロパティを追加できます。
dynamic document = System.Text.Json.JsonSerializer.Deserialize<System.Dynamic.ExpandoObject>( @"{ ""prop1"": ""value1"", ""prop2"": ""value2""}"); ((IDictionary<string, object>)document).Add("prop3", "value3"); foreach (var m in document) Console.WriteLine($"key:{m.Key}, Value:{m.Value}"); Console.WriteLine($"key:prop3, Value:{((dynamic)document).prop3}"); // key:prop1, Value:value1 // key:prop2, Value:value2 // key:prop3, Value:value3 // key:prop3, Value:value3 // と出力される
dynamic
型にして次のようにしてもできます。
((dynamic)document).prop3 = "value3";
プロパティを削除する
IDictionary
の Remove
メソッドでプロパティを削除できます。
System.Dynamic.ExpandoObject document = System.Text.Json.JsonSerializer.Deserialize<System.Dynamic.ExpandoObject>( @"{ ""prop1"": ""value1"", ""prop2"": ""value2""}"); ((IDictionary<string, object>)document).Remove("prop1"); foreach (var m in document) Console.WriteLine($"key:{m.Key}, Value:{m.Value}"); // key:prop2, Value:value2 // と出力される
IDictionary<string, object>
を実装しているので特にキャストしなくても OK です。
document.Remove("prop1", out var _);
■ System.Text.Json.JsonSerializer.Deserialize<System.Dynamic.ExpandoObject> の嬉しくないところ
Json に階層化された構造があると、嬉しい結果になりません。
dynamic document = System.Text.Json.JsonSerializer.Deserialize<System.Dynamic.ExpandoObject>( @"{ ""prop1"": ""a"", ""prop2"": 1, ""prop3"": { ""p31"":31 , ""p32"":32 }}"); // 実行時エラー // Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: ''System.Text.Json.JsonElement' does not contain a definition for 'p31'' Console.WriteLine(document.prop3.p31);
実行時に例外が発生します。
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: ''System.Text.Json.JsonElement' does not contain a definition for 'p31''
階層化構造の子の要素も System.Dynamic.ExpandoObject
になってくれると嬉しいのですが、System.Text.Json.JsonElement
になっています。もじ p31
プロパティの値が欲しければ次のような感じです。
dynamic document = System.Text.Json.JsonSerializer.Deserialize<System.Dynamic.ExpandoObject>( @"{ ""prop1"": ""a"", ""prop2"": 1, ""prop3"": { ""p31"":31 , ""p32"":32 }}"); Console.WriteLine(document.prop3.GetProperty("p31").GetRawText()); // 31 と出力される); Console.WriteLine(document.prop3.GetProperty("p31").GetInt32()); // 31 と出力される);
もう少し丁寧に書くと
dynamic document = System.Text.Json.JsonSerializer.Deserialize<System.Dynamic.ExpandoObject>( @"{ ""prop1"": ""a"", ""prop2"": 1, ""prop3"": { ""p31"":31 , ""p32"":32 }}"); Console.WriteLine(document.prop3.GetProperty("p31").TryGetInt32(out int value) ? value : default); // 31 と出力される);
こんな感じです。