C# - Tratando Json com o namespace System.Text.Json


 Hoje veremos como usar alguns recursos da biblioteca System.Text.Json para trabalhar com JSON.

Trabalhar com o formato JSON atualmente é uma tarefa bem comum, e, para isso existem muitas bibliotecas que podem ser usadas na plataforma .NET;  talvez as mais conhecidas sejam Newtonsoft.Json e a sua evolução :  Json.NET.

A partir da versão 3.0 da plataforma .NET a Microsoft apresentou a biblioteca System.Text.Json como uma alternativa para trabalhar com JSON, e, que se propõe a ter um melhor desempenho.

Dessa forma, o namespace System.Text.Json fornece tipos de alto desempenho e baixa alocação de memória que serializam objetos para texto JavaScript Object Notation (JSON) e desserializam texto JSON para objetos, com suporte UTF-8 integrado.

Este namespace também fornece tipos para ler e gravar texto JSON codificado como UTF-8 e para criar um modelo de objeto de documento (DOM) na memória, que é somente leitura, para acesso aleatório dos elementos JSON em uma visualização estruturada dos dados.

As principais classes desta biblioteca são:

  1. JsonDocument - Uma API de propósito geral que fornece um mecanismo para examinar o conteúdo estrutural de um valor JSON sem criar automaticamente instâncias de valores de dados;
     
  2. JsonSerializer - Uma API mais “avançada” que divide um documento JSON em suas partes constituintes e o expõe por meio de um modelo de objeto de documento e fornece funcionalidade para serializar objetos ou tipos de valor para JSON e para desserializar JSON em objetos ou tipos de valor;
     
  3. Utf8JsonWriter -  Uma API que permite o “controle total” e que permite decidir o que fazer com cada token JSON enquanto mantém o uso de memória no mínimo. É uma API de alto desempenho para gravação somente de encaminhamento e sem cache, de texto JSON codificado em UTF-8;

Provavelmente as tarefas mais realizadas com JSON sejam a serialização e desserialização.

Serializando e Desserializando JSON

1- Serializando JSON

Podemos serializar JSON usando qualquer uma das três classes apresentadas acima:

O método Serialize da classe JsonSerializer (JsonSerializer.Serialize) , talvez seja a forma mais simples de serializar JSON, e converte o valor de um tipo especificado em uma string JSON. O método possui diversas sobrecargas.

Obs: Nos exemplos estou usando os novos recursos Top Level Statement e record do C# 9.

Exemplo:

using System;
using System.Text.Json;

var usuario = new Usuario("Macoratti", "Programador", new Nascimento(30,11,1957));

var usuarioSerializado = JsonSerializer.Serialize(usuario);

Console.WriteLine($" Usuario serializado = \n {usuarioSerializado} \n");
Console.ReadLine();

record Nascimento(int dia, int mes, int ano);
record Usuario(string Nome, string Cargo, Nascimento DataNascimento);

Resultado:

2- Desserializando JSON

A desserialização pode ser feita também usando uma das 3 classes apresentadas.

A forma mais simples e direta é usar o método Deserialize da classe JsonSerializer.

O método Deserialize da classe JsonSerializer , possui várias sobrecargas, e no exemplo usado estamos o método analisa o texto que representa um único valor JSON em uma instância de um tipo especificado.

using System;
using System.Text.Json;

string json = "{\"Nome\":\"Macoratti\",\"Cargo\":\"Programador\"," +
                     "\"DataNascimento\":{\"dia\":12,\"mes\":11,\"ano\":1967}}";

var usuario = JsonSerializer.Deserialize<Usuario>(json);

Console.WriteLine(usuario);

Console.WriteLine(usuario.Nome);
Console.WriteLine(usuario.Cargo);
Console.WriteLine(usuario.DataNascimento);
Console.ReadLine();

record Nascimento(int dia, int mes, int ano);
record Usuario(string Nome, string Cargo, Nascimento DataNascimento);

Resultado:

Nota: Para desserializar a partir do formato UTF-8,  utilize a sobrecarga do método JsonSerializer.Deserialize que usa um ReadOnlySpan<byte> ou um Utf8JsonReader.

Analisando JSON

O método Parse da classe JsonDocument analisa um stream como dados codificados em UTF-8 que representam um único valor JSON em um JsonDocument. O stream é lido até o fim.

using System;
using System.Text.Json;
string dados = " [ {\"nome\": \"Macoratti\", \"cargo\": \"Analista\"}, " +
                         "{\"nome\": \"Miriam\", \"cargo\": \"Secretária\"} ]";
using JsonDocument doc = JsonDocument.Parse(dados);
JsonElement root = doc.RootElement;
Console.WriteLine(root);
var u1 = root[0];
var u2 = root[1];

Console.WriteLine($"{u1}");
Console.WriteLine($"{u2}\n");

Console.WriteLine(u1.GetProperty("nome"));
Console.WriteLine(u1.GetProperty("cargo"));
Console.WriteLine(u2.GetProperty("nome"));
Console.WriteLine(u2.GetProperty("cargo"));
Console.ReadLine();

No exemplo acima a propriedade RootElement obtém o elemento raiz do documento JSON e o operador [] obtém o primeiro e o segundo subelementos do documento JSON. Para obter as propriedades de um elemento usamos o método GetProperty.

Resultado:

Criando um objeto JSON

A classe Utf8JsonWriter possui métodos que permitem gravar o texto sequencialmente sem cache e, por padrão, segue a RFC JSON, com exceção da gravação de comentários.

Um método que tenta gravar um JSON inválido quando a validação é habilitada gera um InvalidOperationException com uma mensagem de erro específica de contexto.

Para poder formatar a saída com indentação e espaço em branco, para ignorar a validação ou para personalizar o comportamento de escape, você tem que criar uma instância da struct JsonWriterOptions e passá-la para o gravador.

Exemplo:  Criando um objeto JSON

using System;
using System.IO;
using System.Text.Json;
using System.Text;
using var ms = new MemoryStream();
using var writer = new Utf8JsonWriter(ms);
writer.WriteStartObject();
writer.WriteString("nome", "Macoratti");
writer.WriteString("cargo", "Programador");
writer.WriteNumber("idade", 54);
writer.WriteEndObject();
writer.Flush();
string json = Encoding.UTF8.GetString(ms.ToArray());
Console.WriteLine("Objeto JSON criado");
Console.WriteLine(json);
Console.ReadLine();

Resultado:

Podemos definir a opção Indented como true para formatar a saída JSON.

No exemplo abaixo estamos gerando o arquivo funcionarios.json na pasta c:\dados\json:

using System;
using System.IO;
using System.Text.Json;
var dados = " [ {\"nome\": \"Macoratti\", \"cargo\": \"Analista\"}, " +
                       "{\"nome\": \"Miriam\", \"cargo\": \"Secretária\"} ]";
JsonDocument jdoc = JsonDocument.Parse(dados);
var nomeArquivo = @"c:/dados/json/funcionarios.json";

using FileStream fs = File.OpenWrite(nomeArquivo);
using var writer = new Utf8JsonWriter(fs, 
                              new JsonWriterOptions { Indented = true });
jdoc.WriteTo(writer);
Console.WriteLine($"Arquivo {nomeArquivo} criado com sucesso");
Console.ReadLine();

Lendo arquivo JSON

A classe Utf8JsonReader possui métodos para processar um texto sequencialmente sem cache e, por padrão, segue estritamente a RFC JSON.

Quando o Utf8JsonReader encontra um JSON inválido, ele gera uma JsonException com informações básicas de erro, como número de linha e posição de byte na linha.

Como esse tipo é uma struct de referência, ele não dá suporte diretamente a Async. No entanto, ele fornece suporte para reentrância a fim de ler dados incompletos e continuar lendo quando mais dados forem apresentados.

Para poder definir a profundidade máxima ao ler ou permitir comentários ignorados, crie uma instância do JsonReaderOptions e passe-a para o leitor.

Exemplo:  Neste exemplo estamos lendo o arquivo funcionarios.json no formato:

[
    {
      "nome": "Macoratti",
      "cargo": "Programador"
    },
    {
      "nome": "Jefferson",
      "cargo": "Engenheiro"
    }
]
using System;
using System.IO;
using System.Text.Json;

var nomeArquivo = @"c:/dados/json/funcionarios.json";

byte[] data = File.ReadAllBytes(nomeArquivo);

Utf8JsonReader reader = new Utf8JsonReader(data);

while (reader.Read())
{
    switch (reader.TokenType)
    {
        case JsonTokenType.StartObject:
            Console.WriteLine("----------------------");
            break;
        case JsonTokenType.EndObject:
            break;
        case JsonTokenType.StartArray:
        case JsonTokenType.EndArray:
            break;
        case JsonTokenType.PropertyName:
            Console.Write($"{reader.GetString()}: ");
            break;
        case JsonTokenType.String:
            Console.WriteLine(reader.GetString());
            break;
        default:
            throw new ArgumentException();

    }
}

No exemplo, lemos dados JSON de um arquivo com Utf8JsonReader que fornece uma API de baixo nível para leitura de dados JSON.

Resultado:

Pegue o código do projeto aqui: Json.zip

Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6

Referências:


José Carlos Macoratti