LoginSignup
1
2

More than 1 year has passed since last update.

System.Text.JsonのJsonDocumentを使ってみる

Last updated at Posted at 2022-05-02

概要

最近の.NET界隈ではJSONをいじくりまわしたいときはSystem.Text.Jsonを使うのがおすすめのようだ。
たまたま使う機会があったので調べてみたところ、主に以下のことができるみたい。

  1. JSON文字列のシリアライズ/デシリアライズ
  2. JsonDocumentによる読み取り専用JSON DOMを使った高速なデータアクセス
  3. JsonNodeによる読み書き可能なJSON DOMを使ったデータ編集

今回は「2. JsonDocumentによる読み取り専用JSON DOMを使った高速なデータアクセス」についてまとめる。

JsonDocumentとJsonNode

そもそもJsonDocumentとは何なのか。Microsoft Docsにわかりやすく書いてある。

JsonDocumentを使用すると、Utf8JsonReaderを使用して読み取り専用DOMを構築することができます。
JsonDocument DOMでは、そのデータにより高速にアクセスできます。

対するJsonNodeは以下のように説明されている。

JsonNodeおよびその派生クラスを使用すると、変更可能なDOMを作成することができます。
JsonNode DOMは作成後に変更できます。JsonDocument DOMは変更できません。

JsonファイルやJSON文字列を読むだけならJsonDocument、編集もしたいならJsonNodeという感じ。
確かに触ってみるとJsonDocumentの機能はかなりサッパリしている。ただ、よくよく調べてみるとJsonDocumentでも簡単な編集はできるようだ。

姉妹記事もご参照ください。

テスト用データ

会社情報(companies)や社員情報(employees)からなる謎のデータ。

{
    "version": "1.0.0",
    "description": "企業情報",
    "companies": [
        {
            "name": "A株式会社",
            "address": "東京都○○市",
            "employees": [
                {
                    "name": "鈴木",
                    "age": 21,
                    "president": false
                },
                {
                    "name": "田中",
                    "age": 30,
                    "president": false
                },
                {
                    "name": "高橋",
                    "age": 54,
                    "president": true
                }
            ]
        },
        {
            "name": "株式会社B",
            "address": "大阪府××市",
            "employees": [
                {
                    "name": "上田",
                    "age": 43,
                    "president": true
                },
                {
                    "name": "小沢",
                    "age": 27,
                    "president": false
                }
            ]
        },
        {
            "name": "C Co., Ltd.",
            "address": "愛知県××市",
            "employees": [
                {
                    "name": "平井",
                    "age": 36,
                    "president": true
                }
            ]
        }
    ]
}

Json文字列を読み込んで分析する

上記のテストデータを読み込んで以下の処理をしている。

  • JSONの構造を走査してすべての要素の型名と値を表示する
    • DisplayJsonElementRecursively()を再帰的に呼び出す
    • オブジェクト(JsonValueKind.Object)や配列(JsonValueKind.Array)には子要素があることに注意する
  • president(社長)がtrueになっている社員を列挙する
    • JSONの構造を泥臭く掘っていくだけ
    • TryXXX()系のメソッドを使ってLINQやるのってどうやるんだろう…
using System;
using System.IO;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

namespace JsonDocumentTest
{
    internal class Program
    {
        internal static string jsonStr =
@"
{
    ""version"": ""1.0.0"",
    ""description"": ""企業情報"",
    ""companies"": [
        {
            ""name"": ""A株式会社"",
            ""address"": ""東京都○○市"",
            ""employees"": [
                {
                    ""name"": ""鈴木"",
                    ""age"": 21,
                    ""president"": false
                },
                {
                    ""name"": ""田中"",
                    ""age"": 30,
                    ""president"": false
                },
                {
                    ""name"": ""高橋"",
                    ""age"": 54,
                    ""president"": true
                }
            ]
        },
        {
            ""name"": ""株式会社B"",
            ""address"": ""大阪府××市"",
            ""employees"": [
                {
                    ""name"": ""上田"",
                    ""age"": 43,
                    ""president"": true
                },
                {
                    ""name"": ""小沢"",
                    ""age"": 27,
                    ""president"": false
                }
            ]
        },
        {
            ""name"": ""C Co., Ltd."",
            ""address"": ""愛知県××市"",
            ""employees"": [
                {
                    ""name"": ""平井"",
                    ""age"": 36,
                    ""president"": true
                }
            ]
        }
    ]
}
";
        static void DisplayJsonElementRecursively(JsonElement element, int level = 0)
        {
            // オブジェクトのとき
            if(element.ValueKind == JsonValueKind.Object)
            {
                string indent = new string('\t', ++level);
                foreach (var child in element.EnumerateObject())
                {
                    // オブジェクトの中身(JsonProperty型)を取り出す
                    Console.WriteLine($"{indent}Name: {child.Name}");
                    Console.WriteLine($"{indent}Kind: {child.Value.ValueKind}");

                    // JsonPropertyからJsonElementを取り出す
                    DisplayJsonElementRecursively(child.Value, level);
                }
            }

            // 配列のとき
            if(element.ValueKind == JsonValueKind.Array)
            {
                string indent = new string('\t', ++level);
                foreach (var child in element.EnumerateArray())
                {
                    // オブジェクトの中身(JsonElement)を取り出す
                    Console.WriteLine($"{indent}Kind: {child.ValueKind}");

                    // JsonElementをそのまま渡す
                    DisplayJsonElementRecursively(child, level);
                }
            }

            // それ以外の型
            if(element.ValueKind == JsonValueKind.Null  ||
               element.ValueKind == JsonValueKind.True  ||
               element.ValueKind == JsonValueKind.False ||
               element.ValueKind == JsonValueKind.Number ||
               element.ValueKind == JsonValueKind.String)
            {
                string indent = new string('\t', level);
                Console.WriteLine($"{indent}Value: {element.ToString()}");
            }
        }


        static void Main(string[] args)
        {
            JsonDocument doc = JsonDocument.Parse(jsonStr);

            Console.WriteLine($"Name: root");
            Console.WriteLine($"Kind: {doc.RootElement.ValueKind}");

            // すべての要素の型名と値を再帰的に表示する
            DisplayJsonElementRecursively(doc.RootElement);


            // 社長(president)の名前を表示する
            if(doc.RootElement.TryGetProperty("companies", out JsonElement companies))
            {
                foreach (var company in companies.EnumerateArray())
                {
                    if (company.TryGetProperty("employees", out JsonElement employees) &&
                        company.TryGetProperty("name", out JsonElement companyName))
                    {
                        foreach (var employee in employees.EnumerateArray())
                        {
                            if (employee.TryGetProperty("president", out JsonElement president))
                            {
                                if (president.ValueKind == JsonValueKind.True ||
                                    president.ValueKind == JsonValueKind.False)
                                {
                                    if (president.GetBoolean() && employee.TryGetProperty("name", out JsonElement name))
                                    {
                                            Console.WriteLine($"President of {companyName.ToString()} is {name.ToString()}");
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

出力

Name: root
Kind: Object
        Name: version
        Kind: String
        Value: 1.0.0
        Name: description
        Kind: String
        Value: 企業情報
        Name: companies
        Kind: Array
                Kind: Object
                        Name: name
                        Kind: String
                        Value: A株式会社
                        Name: address
                        Kind: String
                        Value: 東京都○○市
                        Name: employees
                        Kind: Array
                                Kind: Object
                                        Name: name
                                        Kind: String
                                        Value: 鈴木
                                        Name: age
                                        Kind: Number
                                        Value: 21
                                        Name: president
                                        Kind: False
                                        Value: False
                                Kind: Object
                                        Name: name
                                        Kind: String
                                        Value: 田中
                                        Name: age
                                        Kind: Number
                                        Value: 30
                                        Name: president
                                        Kind: False
                                        Value: False
                                Kind: Object
                                        Name: name
                                        Kind: String
                                        Value: 高橋
                                        Name: age
                                        Kind: Number
                                        Value: 54
                                        Name: president
                                        Kind: True
                                        Value: True
                Kind: Object
                        Name: name
                        Kind: String
                        Value: 株式会社B
                        Name: address
                        Kind: String
                        Value: 大阪府××市
                        Name: employees
                        Kind: Array
                                Kind: Object
                                        Name: name
                                        Kind: String
                                        Value: 上田
                                        Name: age
                                        Kind: Number
                                        Value: 43
                                        Name: president
                                        Kind: True
                                        Value: True
                                Kind: Object
                                        Name: name
                                        Kind: String
                                        Value: 小沢
                                        Name: age
                                        Kind: Number
                                        Value: 27
                                        Name: president
                                        Kind: False
                                        Value: False
                Kind: Object
                        Name: name
                        Kind: String
                        Value: C Co., Ltd.
                        Name: address
                        Kind: String
                        Value: 愛知県××市
                        Name: employees
                        Kind: Array
                                Kind: Object
                                        Name: name
                                        Kind: String
                                        Value: 平井
                                        Name: age
                                        Kind: Number
                                        Value: 36
                                        Name: president
                                        Kind: True
                                        Value: True
President of A株式会社 is 高橋
President of 株式会社B is 上田
President of C Co., Ltd. is 平井

JsonDocumentにデータを追加する

社員データに社員ID風の一意の値(GUID)を追加してみる。

(省略)
static void Main(string[] args)
{
    JsonWriterOptions options = new JsonWriterOptions
    {
        Indented = true,
        // これを指定しないと日本語が正しく出力されない
        Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
    };

    using (MemoryStream ms = new MemoryStream())
    using (Utf8JsonWriter writer = new Utf8JsonWriter(ms, options))
    {
        JsonDocument doc = JsonDocument.Parse(jsonStr);

        // オブジェクトの開始'{'を書き込む
        writer.WriteStartObject();
        foreach (var child1 in doc.RootElement.EnumerateObject())
        {
            if (child1.Name == "companies")
            {
                writer.WritePropertyName(child1.Name);
                // 配列の開始'['を書き込む
                writer.WriteStartArray();
                foreach (var child2 in child1.Value.EnumerateArray())
                {
                    writer.WriteStartObject();
                    foreach (var child3 in child2.EnumerateObject())
                    {
                        if (child3.Name == "employees")
                        {
                            writer.WritePropertyName(child3.Name);
                            writer.WriteStartArray();

                            foreach (var child4 in child3.Value.EnumerateArray())
                            {
                                writer.WriteStartObject();
                                writer.WritePropertyName("id");
                                // GUIDを発行
                                writer.WriteStringValue(Guid.NewGuid().ToString());
                                foreach (var child5 in child4.EnumerateObject())
                                {
                                    child5.WriteTo(writer);
                                }
                                writer.WriteEndObject();
                            }
                            writer.WriteEndArray();
                        }
                        else
                        {
                            child3.WriteTo(writer);
                        }
                    }
                    writer.WriteEndObject();
                }
                writer.WriteEndArray();
            }
            else
            {
                child1.WriteTo(writer);
            }
        }
        writer.WriteEndObject();
        writer.Flush();
        string jsonResult = Encoding.UTF8.GetString(ms.ToArray());
        Console.WriteLine(jsonResult);
    }
}

出力

{
  "version": "1.0.0",
  "description": "企業情報",
  "companies": [
    {
      "name": "A株式会社",
      "address": "東京都○○市",
      "employees": [
        {
          "id": "04ec7efb-28e7-43c4-8f1a-43571d2910b6",
          "name": "鈴木",
          "age": 21,
          "president": false
        },
        {
          "id": "99d9ad44-ef92-4fac-b8f2-2efc1a79661c",
          "name": "田中",
          "age": 30,
          "president": false
        },
        {
          "id": "0c57ba31-5efd-4507-9731-9c2ee47bd98d",
          "name": "高橋",
          "age": 54,
          "president": true
        }
      ]
    },
    {
      "name": "株式会社B",
      "address": "大阪府××市",
      "employees": [
        {
          "id": "85810492-172f-479f-b17e-130657796c80",
          "name": "上田",
          "age": 43,
          "president": true
        },
        {
          "id": "8be5c096-dede-4aae-ab27-d60c08bcfba9",
          "name": "小沢",
          "age": 27,
          "president": false
        }
      ]
    },
    {
      "name": "C Co., Ltd.",
      "address": "愛知県××市",
      "employees": [
        {
          "id": "d8e5c415-428a-4ae2-b7ee-55846c95d883",
          "name": "平井",
          "age": 36,
          "president": true
        }
      ]
    }
  ]
}

JSONのオブジェクトの開始/終了{}や配列の開始/終了[]を意識する必要がある。
削除したり変更したりする場合もUtf8JsonWriterに書き込む値を制御すればよいわけだが、ちょっと複雑すぎる気もする。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2