Serialização JSON com System.Text.Json

A comunicação entre sistemas é algo recorrente no dia a dia do desenvolvedor. Seja a comunicação com um sistema de terceiro, ou mesmo um sistema interno. Entretanto, atualmente o mais comum é a comunicação entre a aplicação e uma API. E nestas situações, geralmente isso ocorre via JSON.

Existem algumas bibliotecas para se trabalhar com JSON no .NET, sendo que as que mais se destacam são Newtonsoft.Json e System.Text.Json. Neste artigo iremos ver a segunda.

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

Tudo começou no .NET Core 2.1

Conforme o ecossistema do .NET Core foi crescendo, o processamento de dados em JSON se tornou uma parte essencial das aplicações da plataforma, principalmente as voltadas para web, como ASP.NET Core e SignalR. Mesmo que fornecesse alguns recursos para trabalhar com JSON, era quase unanime o uso da biblioteca Newtonsoft.Json para este serviço.

Assim, no .NET Core 2.1 esta biblioteca foi integrada a plataforma e se tornou uma dependência padrão de alguns projetos, como o ASP.NET Core. Algo que foi bem aceito, mesmo que em alguns cenários isso gerasse conflitos e/ou limitações (e.g. não era possível utilizar uma versão da biblioteca não suportada pelo ASP.NET Core 2.1).

Sempre visando melhorar a performance da plataforma, durante o desenvolvimento da versão 3, a equipe da Microsoft notou que a biblioteca Newtonsoft.Json poderia ser um limitador para o processamento de JSON. Não que esta biblioteca não seja performática, mas as suas limitações poderiam ser um gargalo para este aspecto do .NET Core.

A equipe pensou em atualizá-la, mas isso iria “quebrar” as antigas versões da mesma, além de outras bibliotecas que dependem dela. Desta forma, no .NET Core 3.0 foi introduzido uma nova biblioteca JSON, a System.Text.Json.

Visando alta performance, esta biblioteca não é equivalente a Newtonsoft.Json, então não dá para substituir uma pela outra. Em alguns casos, o uso da primeira ainda é recomendado e em caso de dúvidas, a Microsoft fornece uma tabela comparando os recursos de cada uma.

Serialização e Deserialização com System.Text.Json

A biblioteca System.Text.Json fornece três formas de se trabalhar com JSON:

  • JsonSerializer: API de uso geral, utilizada para serializar e desserializar JSON em objetos POCO;
  • JsonDocument: API mais avançada, que permite que o JSON seja quebrado e se torne “navegável”;
  • Utf8JsonReader: API que dá controle total sobre o JSON e permite que o desenvolvedor decida como cada token deve ser tratado.

O uso de cada uma irá depender do objetivo, entretanto, por ser bem mais avançada, neste artigo não iremos abordar o Utf8JsonReader. Veremos às demais a seguir.

JsonSerializer

É bem comum necessitar desserializar um JSON em um objeto. Caso este JSON não tenha nenhuma particularidade, isso pode ser feito de forma simples com o método Deserialize:

using System;
using System.Text.Json;

var json = @"{""Nome"":""Carlos Silva"",""Idade"":33}";

var pessoa = JsonSerializer.Deserialize<Pessoa>(json);
Console.WriteLine(pessoa.Nome);
Console.WriteLine(pessoa.Idade);

public class Pessoa
{
    public String Nome { get; set; }
    public int Idade { get; set; }
}

Se o JSON definir algo fora do padrão, como um valor numérico como string:

var json = @"{""Nome"":""Carlos Silva"",""Idade"": ""33""}";

Pode ser utilizado o JsonSerializerOptions para indicar que este valor deve ser contido para numérico:

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

var options = new JsonSerializerOptions
{
    NumberHandling = JsonNumberHandling.AllowReadingFromString
};

var json = @"{""Nome"":""Carlos Silva"",""Idade"": ""33""}";

var pessoa = JsonSerializer.Deserialize<Pessoa>(json, options);
Console.WriteLine(pessoa.Nome);
Console.WriteLine(pessoa.Idade);

public class Pessoa
{
    public String Nome { get; set; }
    public int Idade { get; set; }
}

Se o processo for o inverso, converter um objeto para JSON, pode ser utilizado o método Serialize:

using System;
using System.Text.Json;

var pessoa = new Pessoa { Nome = "Carlos Silva", Idade = 33 };

var json = JsonSerializer.Serialize(pessoa);
Console.WriteLine(json); // {"Nome":"Carlos Silva","Idade":33}

public class Pessoa
{
    public String Nome { get; set; }
    public int Idade { get; set; }
}

Este método também aceita opções:

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

var options = new JsonSerializerOptions
{
    //Lê valores numéricos definidos como string e escreve valores numéricos como strings
    NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString,
    //Identa o JSON gerado
    WriteIndented = true,
    //Ignora propriedades com valor nulo ou padrão
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
};

var pessoa = new Pessoa { Nome = "Carlos Silva", Idade = 33 };

var json = JsonSerializer.Serialize(pessoa, options);
Console.WriteLine(json);
// {
//   "Nome": "Carlos Silva",
//   "Idade": "33"
// }

public class Pessoa
{
    public String Nome { get; set; }
    public int Idade { get; set; }
    public List<String> Telefones { get; set; }
}

JsonDocument

O JsonDocument deve ser utilizado para situações onde se possui o JSON, mas não se deseja que seja desserializado para um objeto POCO. Por exemplo, se a intenção for acessar algum elemento dele:

using System;
using System.Text.Json;

var json = @"{""Nome"":""Carlos Silva"",""Idade"":33, ""Telefones"": { ""celular"": ""11-99999-9999"", ""comercial"": ""11-4444-4444""}}";

var obj = JsonDocument.Parse(json);
var nome = obj.RootElement.GetProperty("Nome").GetString();
var celular = obj.RootElement.GetProperty("Telefones").GetProperty("celular").ToString();
Console.WriteLine(nome);//Carlos Silva
Console.WriteLine(celular);//11-99999-9999

Quando se sabe a localização deste elemento, o seu acesso será simples. Entretanto, e se for necessário pesquisar o elemento dentro do JSON? Para estas situações pode ser utilizado os métodos EnumerateObject e/ou EnumerateArray, que permitem percorrer o JSON:

using System;
using System.Linq;
// using System.Collections.Generic;
using System.Text.Json;
// using System.Text.Json.Serialization;

var json = @"{""Nome"":""Carlos Silva"",""Idade"":33, ""Telefones"": { ""celular"": ""11-99999-9999"", ""comercial"": ""11-4444-4444""}}";

var obj = JsonDocument.Parse(json);
var telefones = obj.RootElement.EnumerateObject()
                   .Where(jsonProperty => jsonProperty.Name.Contains("Telefones") 
                                    && jsonProperty.Value.ValueKind == JsonValueKind.Object)
                   .Select(jsonProperty => jsonProperty.Value.GetProperty("comercial"));
foreach(var telefone in telefones)
    Console.WriteLine(telefone);//11-4444-4444

Serialização e desserialização são operações “caras”, que o JsonDocument procura diminuir mantendo a alocação de memória ao mínimo. Desta forma, ele deve ser utilizado, principalmente quando o JSON for muito complexo para uma classe POCO, for necessário acessar apenas algumas partes dele e/ou não se saiba o formato dos seus dados.

C# (C Sharp) Intermediário
Curso de C# (C Sharp) Intermediário
CONHEÇA O CURSO

Finalizando

Deste que foi lançada a System.Text.Json vem recebendo recursos e melhorias. Ainda não é uma biblioteca substituta para a Newtonsoft.Json (algo que não é a intenção, então não deve ocorrer). Mas devido a sua performance, sempre que ela atender as suas necessidades, opte pelo seu uso.

Então fico por aqui. Até à próxima.

Deixe seu comentário

Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.

© 2004 - 2019 TreinaWeb Tecnologia LTDA - CNPJ: 06.156.637/0001-58 Av. Paulista, 1765, Conj 71 e 72 - Bela Vista - São Paulo - SP - 01311-200