Compartilhar via


Como usar um modelo de objeto de documento do JSON em System.Text.Json

Este artigo mostra como usar um DOM (modelo de objeto de documento) do JSON para acesso aleatório a dados em um conteúdo do JSON.

Opções de DOM JSON

Trabalhar com um DOM é uma alternativa à desserialização com JsonSerializer quando:

  • Você não tem um tipo para desserializar.
  • O JSON que você recebe não tem um esquema fixo e deve ser inspecionado para saber o que ele contém.

System.Text.Json fornece duas maneiras de criar um DOM JSON:

  • JsonDocument fornece a capacidade de criar um DOM somente leitura usando Utf8JsonReader. Os elementos JSON que compõem o conteúdo podem ser acessados pelo tipo JsonElement. O tipo JsonElement fornece os enumeradores de objeto e de matriz JSON junto com as APIs para converter o texto JSON em tipos .NET comuns. JsonDocument expõe uma propriedade RootElement. Para obter mais informações, consulte Usar JsonDocument posteriormente neste artigo.

  • JsonNode e as classes que derivam dele no namespace System.Text.Json.Nodes fornecem a capacidade de criar um DOM mutável. Os elementos JSON que compõem o conteúdo podem ser acessados ​​por meio dos tipos JsonNode, JsonObject, JsonArray, JsonValue e JsonElement. Para obter mais informações, consulte Usar JsonNode posteriormente neste artigo.

Considere os seguintes fatores ao escolher entre JsonDocument e JsonNode:

  • O DOM JsonNode pode ser alterado depois de criado. O DOM JsonDocument é imutável.
  • O DOM JsonDocument fornece acesso mais rápido aos seus dados.

Use JsonNode.

O exemplo a seguir mostra como usar JsonNode e os outros tipos no namespace System.Text.Json.Nodes para:

  • Criar um DOM com base em uma cadeia de caracteres JSON
  • Escreva JSON de um DOM.
  • Obtenha um valor, objeto ou matriz de um 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"
    }
}

Criar um DOM JsonNode com inicializadores de objeto e fazer alterações

O exemplo a seguir mostra como:

  • Crie um DOM usando inicializadores de objeto.
  • Faça alterações em um 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
        //    }
        //  }
        //}
    }
}

Desserializar subseções de um conteúdo JSON

O exemplo a seguir mostra como usar jsonNode para navegar até uma subseção de uma árvore JSON e desserializar um único valor, um tipo personalizado ou uma matriz dessa subseção.

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
    }
}

Exemplo de nota média JsonNode

O exemplo a seguir seleciona uma matriz JSON que tem valores inteiros e calcula um valor médio:

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

O código anterior:

  • Calcula uma nota média para objetos em uma matriz Students que tem uma propriedade Grade.
  • Atribui uma nota padrão de 70 para alunos sem nota.
  • Obtém o número de alunos da propriedade Count de JsonArray.

JsonNode com JsonSerializerOptions

Você pode usar JsonSerializer para serializar e desserializar uma instância de JsonNode. No entanto, se você usar uma sobrecarga que leva JsonSerializerOptions, a instância de opções só será usada para obter conversores personalizados. Outros recursos da instância de opções não são usados. Por exemplo, se você definir JsonSerializerOptions.DefaultIgnoreCondition como WhenWritingNull e chamar JsonSerializer com uma sobrecarga que leva JsonSerializerOptions, as propriedades nulas não serão ignoradas.

A mesma limitação se aplica aos métodos JsonNode que usam um parâmetro JsonSerializerOptions: WriteTo(Utf8JsonWriter, JsonSerializerOptions) e ToJsonString(JsonSerializerOptions). Essas APIs usam JsonSerializerOptions apenas para obter conversores personalizados.

O exemplo a seguir ilustra o resultado do uso de métodos que usam um parâmetro JsonSerializerOptions e serializam uma instância 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; }
}

Se você precisar de recursos JsonSerializerOptions diferentes de conversores personalizados, use JsonSerializer com destinos fortemente tipados (como a classe Person neste exemplo) em vez de JsonNode.

Manipular ordem de propriedade

JsonObject é um dos elementos no payload de um JsonNode, e representa um objeto JSON mutável. Mesmo que o tipo seja modelado como um IDictionary<string, JsonNode>, em que cada entrada é uma propriedade do objeto, ele encapsula uma ordem de propriedade implícita. No entanto, APIs como Insert(Int32, String, JsonNode) e RemoveAt(Int32) modelam efetivamente o tipo como um dicionário ordenado, permitindo que você insira e remova itens em um índice específico. Essas APIs permitem modificações em instâncias de objeto que podem influenciar diretamente a ordem das propriedades.

O código a seguir mostra um exemplo de adição ou movimentação de uma propriedade específica para o início do objeto.

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;
}

Comparar JsonNodes

Para comparar dois objetos JsonNode quanto à igualdade, incluindo seus elementos descendentes, use o método JsonNode.DeepEquals(JsonNode, JsonNode).

Use JsonDocument.

O exemplo a seguir mostra como usar a classe JsonDocument para acesso aleatório a dados em uma cadeia de caracteres 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}")

O código anterior:

  • Pressupõe que o JSON a ser analisado esteja em uma cadeia de caracteres chamada jsonString.
  • Calcula uma nota média para objetos em uma matriz Students que tem uma propriedade Grade.
  • Atribui uma nota padrão de 70 para alunos sem nota.
  • Cria a instância JsonDocument em uma using instrução porque JsonDocument implementa IDisposable. Depois que uma instância JsonDocument é descartada, você perde o acesso a todas as suas instâncias JsonElement também. Para manter o acesso a uma instância JsonElement, faça uma cópia dela antes que a instância pai JsonDocument seja descartada. Para fazer uma cópia, chame JsonElement.Clone. Para obter mais informações, consulte JsonDocument é IDisposable.

O código de exemplo anterior conta os alunos incrementando uma variável count com cada iteração. Uma alternativa é chamar GetArrayLength, conforme mostrado no exemplo a seguir:

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}")

Aqui está um exemplo do JSON que este código processa:

{
  "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
}

Para um exemplo semelhante que usa JsonNode em vez de JsonDocument, consulte Exemplo de nota média de JsonNode.

Como pesquisar um JsonDocument e JsonElement como subelementos

As pesquisas no JsonElement exigem uma pesquisa sequencial das propriedades e, portanto, são relativamente lentas (por exemplo, ao usar TryGetProperty). System.Text.Json foi criado para minimizar o tempo inicial de análise em vez do tempo de pesquisa. Portanto, use as seguintes abordagens para otimizar o desempenho ao pesquisar por meio de um objeto JsonDocument:

  • Use os enumeradores internos (EnumerateArray e EnumerateObject) em vez de fazer sua própria indexação ou loops.
  • Não faça uma pesquisa sequencial no todo JsonDocument por meio de cada propriedade usando RootElement. Em vez disso, pesquise objetos JSON aninhados com base na estrutura conhecida dos dados JSON. Por exemplo, os exemplos de código anteriores procuram uma propriedade Grade em objetos Student percorrendo os objetos Student e obtendo o valor de Grade para cada um, em vez de pesquisar em todos os objetos JsonElement procurando por propriedades Grade. Fazer o último resultaria em passagens desnecessárias sobre os mesmos dados.

Comparar JsonElements

Para comparar dois objetos JsonElement quanto à igualdade, incluindo seus elementos descendentes, use o método 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.

Usar JsonDocument para gravar JSON

O exemplo a seguir mostra como escrever JSON de um JsonDocument:

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()

O código anterior:

  • Lê um arquivo JSON, carrega os dados em um JsonDocument e grava JSON formatado (bem impresso) em um arquivo.
  • Usa JsonDocumentOptions para especificar que os comentários no JSON de entrada são permitidos, mas ignorados.
  • Quando terminar, chama o gravador Flush. Uma alternativa é deixar o gravador liberar automaticamente quando ele for descartado.

Aqui está um exemplo de entrada JSON a ser processada pelo código de exemplo:

{"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}

O resultado é a seguinte saída JSON bastante impressa:

{
  "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 cria uma exibição na memória dos dados em um buffer em pool. Portanto, o tipo JsonDocument implementa IDisposable e precisa ser usado dentro de um bloco using.

Retorne apenas JsonDocument de sua API se você quiser transferir a propriedade de tempo de vida e descartar a responsabilidade para o chamador. Na maioria dos cenários, isso não é necessário. Se o chamador precisar trabalhar com todo o documento JSON, retorne o Clone do RootElement, que é um JsonElement. Se o chamador precisar trabalhar com um elemento específico no documento JSON, retorne o Clone desse JsonElement. Se você retornar o RootElement ou um subconjunto diretamente sem fazer um Clone, o chamador não poderá acessar o retornado JsonElement depois que o proprietário JsonDocument for descartado.

Aqui está um exemplo que exige que você faça um Clone:

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

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

O código anterior espera um JsonElement que contenha uma propriedade fileName. Ele abre um arquivo JSON e cria JsonDocument. O método assume que o chamador deseja trabalhar com todo o documento, então ele retorna Clone de RootElement.

Se você receber JsonElement e estiver retornando um subelemento, não será necessário retornar um subconjunto Clone. O chamador é responsável por manter ativo o JsonDocument pertencente ao JsonElement passado. Por exemplo:

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

JsonDocument com JsonSerializerOptions

Você pode usar JsonSerializer para serializar e desserializar uma instância de JsonDocument. No entanto, a implementação para leitura e gravação de instâncias JsonDocument usando JsonSerializer é um wrapper por JsonDocument.ParseValue(Utf8JsonReader) e JsonDocument.WriteTo(Utf8JsonWriter). Esse wrapper não encaminha nenhum JsonSerializerOptions (recursos de serializador) para Utf8JsonReader ou Utf8JsonWriter. Por exemplo, se você definir JsonSerializerOptions.DefaultIgnoreCondition como WhenWritingNull e chamar JsonSerializer com uma sobrecarga que leva JsonSerializerOptions, as propriedades nulas não serão ignoradas.

O exemplo a seguir ilustra o resultado do uso de métodos que usam um parâmetro JsonSerializerOptions e serializam uma instância 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; }
}

Se você precisar de recursos JsonSerializerOptions, use JsonSerializer com destinos fortemente tipados (como a classe Person neste exemplo) em vez de JsonDocument.

Confira também