次の方法で共有


System.Text.Json で JSON ドキュメント オブジェクト モデルを使用する方法

この記事では、JSON ドキュメント オブジェクト モデル (DOM) を使って、JSON ペイロードのデータにランダム アクセスする方法について説明します。

JSON DOM の選択肢

次の場合、DOM を使うことは、JsonSerializer による逆シリアル化の代替手段です。

  • 逆シリアル化する型がない。
  • 受信した JSON に固定スキーマがなく、含まれている内容を確認するために検査する必要がある。

System.Text.Json には、JSON DOM を構築する 2 つの方法が用意されています。

  • JsonDocument を使用すると、Utf8JsonReader を使用して読み取り専用 DOM を構築することができます。 ペイロードを構成する JSON 要素には、JsonElement 型を使用してアクセスできます。 JsonElement 型では、配列とオブジェクト列挙子と共に、JSON テキストを一般的な .NET 型に変換する API が提供されます。 JsonDocument では RootElement プロパティが公開されます。 詳細については、この記事で後述する「JsonDocument の使用」を参照してください。

  • System.Text.Json.Nodes 名前空間の JsonNode およびその派生クラスを使用すると、変更可能な DOM を作成することができます。 ペイロードを構成する JSON 要素には、JsonNodeJsonObjectJsonArrayJsonValueJsonElement 型を使用してアクセスできます。 詳細については、この記事で後述する「JsonNode の使用」を参照してください。

JsonDocumentJsonNode のどちらを使用するか選ぶときは、次の要素を考慮してください。

  • JsonNode DOM は作成後に変更できます。 JsonDocument DOM は変更できません。
  • JsonDocument DOM では、そのデータにより高速にアクセスできます。

JsonNode を使用します

以下の例では、System.Text.Json.Nodes 名前空間の JsonNode と他の型を使用して、次の操作を行う方法を示します。

  • JSON 文字列から DOM を作成する
  • DOM から JSON を書き込む。
  • DOM から値、オブジェクト、または配列を取得する。
using System.Text.Json;
using System.Text.Json.Nodes;

namespace JsonNodeFromStringExample;

public class Program
{
    public static void Main()
    {
        string jsonString = """
            {
              "Date": "2019-08-01T00:00:00",
              "Temperature": 25,
              "Summary": "Hot",
              "DatesAvailable": [
                "2019-08-01T00:00:00",
                "2019-08-02T00:00:00"
              ],
              "TemperatureRanges": {
                  "Cold": {
                      "High": 20,
                      "Low": -10
                  },
                  "Hot": {
                      "High": 60,
                      "Low": 20
                  }
              }
            }
            """;
        // Create a JsonNode DOM from a JSON string.
        JsonNode forecastNode = JsonNode.Parse(jsonString)!;

        // Write JSON from a JsonNode
        var options = new JsonSerializerOptions { WriteIndented = true };
        Console.WriteLine(forecastNode!.ToJsonString(options));
        // output:
        //{
        //  "Date": "2019-08-01T00:00:00",
        //  "Temperature": 25,
        //  "Summary": "Hot",
        //  "DatesAvailable": [
        //    "2019-08-01T00:00:00",
        //    "2019-08-02T00:00:00"
        //  ],
        //  "TemperatureRanges": {
        //    "Cold": {
        //      "High": 20,
        //      "Low": -10
        //    },
        //    "Hot": {
        //      "High": 60,
        //      "Low": 20
        //    }
        //  }
        //}

        // Get value from a JsonNode.
        JsonNode temperatureNode = forecastNode!["Temperature"]!;
        Console.WriteLine($"Type={temperatureNode.GetType()}");
        Console.WriteLine($"JSON={temperatureNode.ToJsonString()}");
        //output:
        //Type = System.Text.Json.Nodes.JsonValue`1[System.Text.Json.JsonElement]
        //JSON = 25

        // Get a typed value from a JsonNode.
        int temperatureInt = (int)forecastNode!["Temperature"]!;
        Console.WriteLine($"Value={temperatureInt}");
        //output:
        //Value=25

        // Get a typed value from a JsonNode by using GetValue<T>.
        temperatureInt = forecastNode!["Temperature"]!.GetValue<int>();
        Console.WriteLine($"TemperatureInt={temperatureInt}");
        //output:
        //Value=25

        // Get a JSON object from a JsonNode.
        JsonNode temperatureRanges = forecastNode!["TemperatureRanges"]!;
        Console.WriteLine($"Type={temperatureRanges.GetType()}");
        Console.WriteLine($"JSON={temperatureRanges.ToJsonString()}");
        //output:
        //Type = System.Text.Json.Nodes.JsonObject
        //JSON = { "Cold":{ "High":20,"Low":-10},"Hot":{ "High":60,"Low":20} }

        // Get a JSON array from a JsonNode.
        JsonNode datesAvailable = forecastNode!["DatesAvailable"]!;
        Console.WriteLine($"Type={datesAvailable.GetType()}");
        Console.WriteLine($"JSON={datesAvailable.ToJsonString()}");
        //output:
        //datesAvailable Type = System.Text.Json.Nodes.JsonArray
        //datesAvailable JSON =["2019-08-01T00:00:00", "2019-08-02T00:00:00"]

        // Get an array element value from a JsonArray.
        JsonNode firstDateAvailable = datesAvailable[0]!;
        Console.WriteLine($"Type={firstDateAvailable.GetType()}");
        Console.WriteLine($"JSON={firstDateAvailable.ToJsonString()}");
        //output:
        //Type = System.Text.Json.Nodes.JsonValue`1[System.Text.Json.JsonElement]
        //JSON = "2019-08-01T00:00:00"

        // Get a typed value by chaining references.
        int coldHighTemperature = (int)forecastNode["TemperatureRanges"]!["Cold"]!["High"]!;
        Console.WriteLine($"TemperatureRanges.Cold.High={coldHighTemperature}");
        //output:
        //TemperatureRanges.Cold.High = 20

        // Parse a JSON array
        var datesNode = JsonNode.Parse(@"[""2019-08-01T00:00:00"",""2019-08-02T00:00:00""]");
        JsonNode firstDate = datesNode![0]!.GetValue<DateTime>();
        Console.WriteLine($"firstDate={ firstDate}");
        //output:
        //firstDate = "2019-08-01T00:00:00"
    }
}

オブジェクト初期化子を使用して JsonNode DOM を作成し、変更を行う

以下の例では、次のことを行っています。

  • オブジェクト初期化子を使用して DOM を作成する。
  • DOM に変更を加える。
using System.Text.Json;
using System.Text.Json.Nodes;

namespace JsonNodeFromObjectExample;

public class Program
{
    public static void Main()
    {
        // Create a new JsonObject using object initializers.
        var forecastObject = new JsonObject
        {
            ["Date"] = new DateTime(2019, 8, 1),
            ["Temperature"] = 25,
            ["Summary"] = "Hot",
            ["DatesAvailable"] = new JsonArray(
                new DateTime(2019, 8, 1), new DateTime(2019, 8, 2)),
            ["TemperatureRanges"] = new JsonObject
            {
                ["Cold"] = new JsonObject
                {
                    ["High"] = 20,
                    ["Low"] = -10
                }
            },
            ["SummaryWords"] = new JsonArray("Cool", "Windy", "Humid")
        };

        // Add an object.
        forecastObject!["TemperatureRanges"]!["Hot"] =
            new JsonObject { ["High"] = 60, ["Low"] = 20 };

        // Remove a property.
        forecastObject.Remove("SummaryWords");

        // Change the value of a property.
        forecastObject["Date"] = new DateTime(2019, 8, 3);

        var options = new JsonSerializerOptions { WriteIndented = true };
        Console.WriteLine(forecastObject.ToJsonString(options));
        //output:
        //{
        //  "Date": "2019-08-03T00:00:00",
        //  "Temperature": 25,
        //  "Summary": "Hot",
        //  "DatesAvailable": [
        //    "2019-08-01T00:00:00",
        //    "2019-08-02T00:00:00"
        //  ],
        //  "TemperatureRanges": {
        //    "Cold": {
        //      "High": 20,
        //      "Low": -10
        //    },
        //    "Hot": {
        //      "High": 60,
        //      "Low": 20
        //    }
        //  }
        //}
    }
}

JSON ペイロードのサブセクションを逆シリアル化する

次の例では、JsonNode を使用して、JSON ツリーのサブセクションに移動し、そのサブセクションからの単一の値、カスタム型、または配列を逆シリアル化する方法を示しています。

using System.Text.Json;
using System.Text.Json.Nodes;

namespace JsonNodePOCOExample;

public class TemperatureRanges : Dictionary<string, HighLowTemps>
{
}

public class HighLowTemps
{
    public int High { get; set; }
    public int Low { get; set; }
}

public class Program
{
    public static DateTime[]? DatesAvailable { get; set; }

    public static void Main()
    {
        string jsonString = """
            {
              "Date": "2019-08-01T00:00:00",
              "Temperature": 25,
              "Summary": "Hot",
              "DatesAvailable": [
                "2019-08-01T00:00:00",
                "2019-08-02T00:00:00"
              ],
              "TemperatureRanges": {
                  "Cold": {
                      "High": 20,
                      "Low": -10
                  },
                  "Hot": {
                      "High": 60,
                      "Low": 20
                  }
              }
            }
            """;
        // Parse all of the JSON.
        JsonNode forecastNode = JsonNode.Parse(jsonString)!;

        // Get a single value
        int hotHigh = forecastNode["TemperatureRanges"]!["Hot"]!["High"]!.GetValue<int>();
        Console.WriteLine($"Hot.High={hotHigh}");
        // output:
        //Hot.High=60

        // Get a subsection and deserialize it into a custom type.
        JsonObject temperatureRangesObject = forecastNode!["TemperatureRanges"]!.AsObject();
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream);
        temperatureRangesObject.WriteTo(writer);
        writer.Flush();
        TemperatureRanges? temperatureRanges = 
            JsonSerializer.Deserialize<TemperatureRanges>(stream.ToArray());
        Console.WriteLine($"Cold.Low={temperatureRanges!["Cold"].Low}, Hot.High={temperatureRanges["Hot"].High}");
        // output:
        //Cold.Low=-10, Hot.High=60

        // Get a subsection and deserialize it into an array.
        JsonArray datesAvailable = forecastNode!["DatesAvailable"]!.AsArray()!;
        Console.WriteLine($"DatesAvailable[0]={datesAvailable[0]}");
        // output:
        //DatesAvailable[0]=8/1/2019 12:00:00 AM
    }
}

JsonNode の平均グレードの例

次の例では、整数値を持つ JSON 配列を選択し、平均値を計算します。

using System.Text.Json.Nodes;

namespace JsonNodeAverageGradeExample;

public class Program
{
    public static void Main()
    {
        string jsonString = """
            {
              "Class Name": "Science",
              "Teacher\u0027s Name": "Jane",
              "Semester": "2019-01-01",
              "Students": [
                {
                  "Name": "John",
                  "Grade": 94.3
                },
                {
                  "Name": "James",
                  "Grade": 81.0
                },
                {
                  "Name": "Julia",
                  "Grade": 91.9
                },
                {
                  "Name": "Jessica",
                  "Grade": 72.4
                },
                {
                  "Name": "Johnathan"
                }
              ],
              "Final": true
            }
            """;
        double sum = 0;
        JsonNode document = JsonNode.Parse(jsonString)!;

        JsonNode root = document.Root;
        JsonArray studentsArray = root["Students"]!.AsArray();

        int count = studentsArray.Count;
        foreach (JsonNode? student in studentsArray)
        {
            if (student?["Grade"] is JsonNode gradeNode)
            {
                sum += (double)gradeNode;
            }
            else
            {
                sum += 70;
            }
        }

        double average = sum / count;
        Console.WriteLine($"Average grade : {average}");
    }
}
// output:
//Average grade : 81.92

上記のコードでは次の操作が行われます。

  • Grade プロパティを持つ Students 配列内のオブジェクトの平均グレードを計算します。
  • グレードのない学生には既定のグレード 70 を割り当てます。
  • JsonArrayCount プロパティから学生の数を取得します。

JsonSerializerOptions を含む JsonNode

JsonSerializer を使用すると、JsonNode のインスタンスをシリアル化したり、逆シリアル化したりすることができます。 ただし、JsonSerializerOptions を取るオーバーロードを使用する場合、オプション インスタンスはカスタム コンバーターを取得するためにのみ使用されます。 オプション インスタンスのその他の機能は使用されていません。 たとえば、JsonSerializerOptions.DefaultIgnoreConditionWhenWritingNull に設定し、JsonSerializerOptions を取るオーバーロードを使用して JsonSerializer を呼び出した場合、null のプロパティは無視されません。

同じ制限が、JsonSerializerOptions パラメーターとして WriteTo(Utf8JsonWriter, JsonSerializerOptions)ToJsonString(JsonSerializerOptions) を取る JsonNode メソッドに適用されます。 これらの API では、カスタム コンバーターを取得するためにのみ JsonSerializerOptions を使用します。

次の例は、JsonSerializerOptions パラメーターを取り、JsonNode インスタンスをシリアル化するメソッドを使用した結果を示しています。

using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace JsonNodeWithJsonSerializerOptions;

public class Program
{
    public static void Main()
    {
        Person person = new() { Name = "Nancy" };

        // Default serialization - Address property included with null token.
        // Output: {"Name":"Nancy","Address":null}
        string personJsonWithNull = JsonSerializer.Serialize(person);
        Console.WriteLine(personJsonWithNull);

        // Serialize and ignore null properties - null Address property is omitted
        // Output: {"Name":"Nancy"}
        JsonSerializerOptions options = new()
        {
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
        };
        string personJsonWithoutNull = JsonSerializer.Serialize(person, options);
        Console.WriteLine(personJsonWithoutNull);

        // Ignore null properties doesn't work when serializing JsonNode instance
        // by using JsonSerializer.
        // Output: {"Name":"Nancy","Address":null}
        JsonNode? personJsonNode = JsonSerializer.Deserialize<JsonNode>(personJsonWithNull);
        personJsonWithNull = JsonSerializer.Serialize(personJsonNode, options);
        Console.WriteLine(personJsonWithNull);

        // Ignore null properties doesn't work when serializing JsonNode instance
        // by using JsonNode.ToJsonString method.
        // Output: {"Name":"Nancy","Address":null}
        personJsonWithNull = personJsonNode!.ToJsonString(options);
        Console.WriteLine(personJsonWithNull);

        // Ignore null properties doesn't work when serializing JsonNode instance
        // by using JsonNode.WriteTo method.
        // Output: {"Name":"Nancy","Address":null}
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream);
        personJsonNode!.WriteTo(writer, options);
        writer.Flush();
        personJsonWithNull = Encoding.UTF8.GetString(stream.ToArray());
        Console.WriteLine(personJsonWithNull);
    }
}

public class Person
{
    public string? Name { get; set; }
    public string? Address { get; set; }
}

カスタム コンバーター以外の JsonSerializerOptions の機能が必要な場合は、JsonNode ではなく、厳密に型指定されたターゲット (この例の Person クラスなど) を指定して JsonSerializer を使用します。

プロパティの順序を操作する

JsonObject は、JsonNode のペイロード内の要素の 1 つであり、変更可能な JSON オブジェクトを表します。 この型は IDictionary<string, JsonNode> としてモデル化されており、各エントリはオブジェクトのプロパティであり、暗黙的なプロパティの順序がカプセル化されています。 ただし、Insert(Int32, String, JsonNode)RemoveAt(Int32) などの API では、特定のインデックスでの項目の挿入と削除を可能にすることで、この型を実質的に順序付きディクショナリとしてモデル化しています。 これらの API を使用すると、オブジェクト インスタンスを変更して、プロパティの順序に直接影響を与えることができます。

次のコードは、特定のプロパティをオブジェクトの先頭に追加または移動する例を示しています。

var schema = (JsonObject)JsonSerializerOptions.Default.GetJsonSchemaAsNode(typeof(MyPoco));

JsonNode? idValue;
switch (schema.IndexOf("$id"))
{
    // $id property missing.
    case < 0:
        idValue = (JsonNode)"https://example.com/schema";
        schema.Insert(0, "$id", idValue);
        break;

    // $id property already at the start of the object.
    case 0:
        break;

    // $id exists but not at the start of the object.
    case int index:
        idValue = schema[index];
        schema.RemoveAt(index);
        schema.Insert(0, "$id", idValue);
        break;
}

JsonNode を比較する

2 つの JsonNode オブジェクトを比較し、その子要素も含めて等しいかどうかを確認するには、JsonNode.DeepEquals(JsonNode, JsonNode) メソッドを使用します。

JsonDocument を使用します

次の例は、JsonDocument クラスを使用して JSON 文字列内のデータにランダム アクセスする方法を示しています。

double sum = 0;
int count = 0;

using (JsonDocument document = JsonDocument.Parse(jsonString))
{
    JsonElement root = document.RootElement;
    JsonElement studentsElement = root.GetProperty("Students");
    foreach (JsonElement student in studentsElement.EnumerateArray())
    {
        if (student.TryGetProperty("Grade", out JsonElement gradeElement))
        {
            sum += gradeElement.GetDouble();
        }
        else
        {
            sum += 70;
        }
        count++;
    }
}

double average = sum / count;
Console.WriteLine($"Average grade : {average}");
Dim sum As Double = 0
Dim count As Integer = 0
Using document As JsonDocument = JsonDocument.Parse(jsonString)
    Dim root As JsonElement = document.RootElement
    Dim studentsElement As JsonElement = root.GetProperty("Students")
    For Each student As JsonElement In studentsElement.EnumerateArray()
        Dim gradeElement As JsonElement = Nothing
        If student.TryGetProperty("Grade", gradeElement) Then
            sum += gradeElement.GetDouble()
        Else
            sum += 70
        End If
        count += 1
    Next
End Using

Dim average As Double = sum / count
Console.WriteLine($"Average grade : {average}")

上記のコードでは次の操作が行われます。

  • 分析する JSON が jsonString という名前の文字列に含まれていると想定します。
  • Grade プロパティを持つ Students 配列内のオブジェクトの平均グレードを計算します。
  • グレードのない学生には既定のグレード 70 を割り当てます。
  • JsonDocument によって IDisposable が実装されるため、using ステートメントJsonDocument インスタンスを作成します。 JsonDocumentインスタンスが破棄されると、そのすべての JsonElement インスタンスにもアクセスできなくなります。 JsonElement インスタンスへのアクセスを保持するには、親 JsonDocument インスタンスが破棄される前に、そのコピーを作成します。 コピーを作成するには、JsonElement.Clone を呼び出します。 詳細については、「JsonDocument は IDisposable」を参照してください。

前のコード例では、反復処理ごとに count 変数をインクリメントして学生をカウントします。 別の方法として、次の例に示すように GetArrayLength を呼び出すこともできます。

double sum = 0;
int count = 0;

using (JsonDocument document = JsonDocument.Parse(jsonString))
{
    JsonElement root = document.RootElement;
    JsonElement studentsElement = root.GetProperty("Students");

    count = studentsElement.GetArrayLength();

    foreach (JsonElement student in studentsElement.EnumerateArray())
    {
        if (student.TryGetProperty("Grade", out JsonElement gradeElement))
        {
            sum += gradeElement.GetDouble();
        }
        else
        {
            sum += 70;
        }
    }
}

double average = sum / count;
Console.WriteLine($"Average grade : {average}");
Dim sum As Double = 0
Dim count As Integer = 0
Using document As JsonDocument = JsonDocument.Parse(jsonString)
    Dim root As JsonElement = document.RootElement
    Dim studentsElement As JsonElement = root.GetProperty("Students")

    count = studentsElement.GetArrayLength()

    For Each student As JsonElement In studentsElement.EnumerateArray()
        Dim gradeElement As JsonElement = Nothing
        If student.TryGetProperty("Grade", gradeElement) Then
            sum += gradeElement.GetDouble()
        Else
            sum += 70
        End If
    Next
End Using

Dim average As Double = sum / count
Console.WriteLine($"Average grade : {average}")

このコードで処理される JSON の例を次に示します。

{
  "Class Name": "Science",
  "Teacher\u0027s Name": "Jane",
  "Semester": "2019-01-01",
  "Students": [
    {
      "Name": "John",
      "Grade": 94.3
    },
    {
      "Name": "James",
      "Grade": 81.0
    },
    {
      "Name": "Julia",
      "Grade": 91.9
    },
    {
      "Name": "Jessica",
      "Grade": 72.4
    },
    {
      "Name": "Johnathan"
    }
  ],
  "Final": true
}

JsonDocument の代わりに JsonNode を使用する同様の例については、「JsonNode の平均グレードの例」を参照してください。

JsonDocument と JsonElement でのサブ要素の検索方法

JsonElement での検索にはプロパティの順次検索が必要になるため、比較的低速になります (たとえば、TryGetProperty を使用する場合)。 System.Text.Json は、検索時間ではなく、初期解析時間を最小限に抑えるように設計されています。 そのため、JsonDocument オブジェクトで検索する場合は、次の方法を使用してパフォーマンスを最適化してください。

  • 独自のインデックス作成やループを実行するのではなく、組み込みの列挙子 (EnumerateArrayEnumerateObject) を使用します。
  • RootElement を使用して、JsonDocument 全体ですべてのプロパティの順次検索を行わないでください。 代わりに、JSON データの既知の構造に基づいて、入れ子になった JSON オブジェクトで検索します。 たとえば上記のコード例では、Student オブジェクトをループし、それぞれの Grade の値を取得して、Student オブジェクトで Grade プロパティを探します。すべての JsonElement オブジェクトで Grade プロパティを検索することは行いません。 後者を行うと、同じデータに対して不要なパスが行われます。

JsonElement を比較する

2 つの JsonElement オブジェクトを比較し、その子要素も含めて等しいかどうかを確認するには、JsonElement.DeepEquals(JsonElement, JsonElement) メソッドを使用します。

JsonElement left = JsonDocument.Parse("10e-3").RootElement;
JsonElement right = JsonDocument.Parse("0.01").RootElement;
bool equal = JsonElement.DeepEquals(left, right);
Console.WriteLine(equal); // True.

JsonDocument を使用して JSON を書き込む

次の例では、JsonDocument から JSON を書き込む方法を示します。

string jsonString = File.ReadAllText(inputFileName);

var writerOptions = new JsonWriterOptions
{
    Indented = true
};

var documentOptions = new JsonDocumentOptions
{
    CommentHandling = JsonCommentHandling.Skip
};

using FileStream fs = File.Create(outputFileName);
using var writer = new Utf8JsonWriter(fs, options: writerOptions);
using JsonDocument document = JsonDocument.Parse(jsonString, documentOptions);

JsonElement root = document.RootElement;

if (root.ValueKind == JsonValueKind.Object)
{
    writer.WriteStartObject();
}
else
{
    return;
}

foreach (JsonProperty property in root.EnumerateObject())
{
    property.WriteTo(writer);
}

writer.WriteEndObject();

writer.Flush();
Dim jsonString As String = File.ReadAllText(inputFileName)

Dim writerOptions As JsonWriterOptions = New JsonWriterOptions With {
    .Indented = True
}

Dim documentOptions As JsonDocumentOptions = New JsonDocumentOptions With {
    .CommentHandling = JsonCommentHandling.Skip
}

Dim fs As FileStream = File.Create(outputFileName)
Dim writer As Utf8JsonWriter = New Utf8JsonWriter(fs, options:=writerOptions)
Dim document As JsonDocument = JsonDocument.Parse(jsonString, documentOptions)

Dim root As JsonElement = document.RootElement

If root.ValueKind = JsonValueKind.[Object] Then
    writer.WriteStartObject()
Else
    Return
End If

For Each [property] As JsonProperty In root.EnumerateObject()
    [property].WriteTo(writer)
Next

writer.WriteEndObject()

writer.Flush()

上記のコードでは次の操作が行われます。

  • JSON ファイルを読み取り、データを JsonDocument に読み込み、書式設定された (整形された) JSON をファイルに書き込みます。
  • JsonDocumentOptions を使用して、入力 JSON 内でコメントは許可されるが無視されることを指定します。
  • 完了したら、ライターに対して Flush を呼び出します。 別の方法として、破棄されたときにライターを自動フラッシュすることもできます。

コード例によって処理される JSON 入力の例を次に示します。

{"Class Name": "Science","Teacher's Name": "Jane","Semester": "2019-01-01","Students": [{"Name": "John","Grade": 94.3},{"Name": "James","Grade": 81.0},{"Name": "Julia","Grade": 91.9},{"Name": "Jessica","Grade": 72.4},{"Name": "Johnathan"}],"Final": true}

その結果は、次のような整形された JSON 出力になります。

{
  "Class Name": "Science",
  "Teacher\u0027s Name": "Jane",
  "Semester": "2019-01-01",
  "Students": [
    {
      "Name": "John",
      "Grade": 94.3
    },
    {
      "Name": "James",
      "Grade": 81.0
    },
    {
      "Name": "Julia",
      "Grade": 91.9
    },
    {
      "Name": "Jessica",
      "Grade": 72.4
    },
    {
      "Name": "Johnathan"
    }
  ],
  "Final": true
}

JsonDocument は IDisposable

JsonDocument では、データのメモリ内ビューがプールされたバッファー内に作成されます。 そのため、JsonDocument 型は IDisposable を実装し、using ブロック内で使用される必要があります。

有効期間中全体の所有権を呼び出し元に移譲し、責任を破棄する場合は、API から JsonDocument のみを返します。 ほとんどのシナリオでは、これは必要ありません。 呼び出し元が JSON ドキュメント全体を操作する必要がある場合は、RootElement (つまり JsonElement) の Clone を返します。 呼び出し元が JSON ドキュメント内の特定の要素を操作する必要がある場合は、その JsonElementClone を返します。 Clone を作成せずに直接 RootElement またはサブ要素を返した場合、呼び出し元は、返された JsonElement には、それを所有する JsonDocument が破棄されるとアクセスできなくなります。

Clone を作成する必要がある例を次に示します。

public JsonElement LookAndLoad(JsonElement source)
{
    string json = File.ReadAllText(source.GetProperty("fileName").GetString());

    using (JsonDocument doc = JsonDocument.Parse(json))
    {
        return doc.RootElement.Clone();
    }
}

上記のコードでは、fileName プロパティを含む JsonElement が想定されています。 これにより、JSON ファイルが開き、JsonDocument が作成されます。 このメソッドでは、呼び出し元がドキュメント全体を操作することが想定されているため、RootElementClone が返されます。

JsonElement を受け取り、サブ要素を返す場合は、サブ要素の Clone を返す必要はありません。 呼び出し元は、渡された JsonElement が属している JsonDocument が破棄されないように維持する役割を負っています。 次に例を示します。

public JsonElement ReturnFileName(JsonElement source)
{
   return source.GetProperty("fileName");
}

JsonSerializerOptions を含む JsonDocument

JsonSerializer を使用すると、JsonDocument のインスタンスをシリアル化したり、逆シリアル化したりすることができます。 ただし、JsonSerializer を使用した JsonDocument インスタンスの読み取りと書き込みの実装は、JsonDocument.ParseValue(Utf8JsonReader)JsonDocument.WriteTo(Utf8JsonWriter) に対するラッパーです。 このラッパーは、どの JsonSerializerOptions (シリアライザー機能) も Utf8JsonReaderUtf8JsonWriter に転送しません。 たとえば、JsonSerializerOptions.DefaultIgnoreConditionWhenWritingNull に設定し、JsonSerializerOptions を取るオーバーロードを使用して JsonSerializer を呼び出した場合、null のプロパティは無視されません。

次の例は、JsonSerializerOptions パラメーターを取り、JsonDocument インスタンスをシリアル化するメソッドを使用した結果を示しています。

using System.Text.Json;
using System.Text.Json.Serialization;

namespace JsonDocumentWithJsonSerializerOptions;

public class Program
{
    public static void Main()
    {
        Person person = new() { Name = "Nancy" };

        // Default serialization - Address property included with null token.
        // Output: {"Name":"Nancy","Address":null}
        string personJsonWithNull = JsonSerializer.Serialize(person);
        Console.WriteLine(personJsonWithNull);

        // Serialize and ignore null properties - null Address property is omitted
        // Output: {"Name":"Nancy"}
        JsonSerializerOptions options = new()
        {
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
        };
        string personJsonWithoutNull = JsonSerializer.Serialize(person, options);
        Console.WriteLine(personJsonWithoutNull);

        // Ignore null properties doesn't work when serializing JsonDocument instance
        // by using JsonSerializer.
        // Output: {"Name":"Nancy","Address":null}
        JsonDocument? personJsonDocument = JsonSerializer.Deserialize<JsonDocument>(personJsonWithNull);
        personJsonWithNull = JsonSerializer.Serialize(personJsonDocument, options);
        Console.WriteLine(personJsonWithNull);
    }
}
public class Person
{
    public string? Name { get; set; }
    public string? Address { get; set; }
}

JsonSerializerOptions の機能が必要な場合は、JsonDocument ではなく、厳密に型指定されたターゲット (この例の Person クラスなど) を指定して JsonSerializer を使用します。

こちらもご覧ください