A serialização e desserialização de objetos para JSON são tarefas comuns em projetos .NET. No ecossistema .NET, o “System.Text.Json” se destaca como uma biblioteca nativa, eficiente e flexível para lidar com JSON. Embora seja amplamente utilizado para casos simples, situações complexas frequentemente surgem, como trabalhar com formatos de data e hora personalizados ou lidar com enums representados como strings no JSON. Neste artigo, exploraremos como o “System.Text.Json” pode ser adaptado para atender a essas demandas, com foco na criação e uso de conversores personalizados.
Introdução ao System.Text.Json
Antes de nos aprofundarmos em técnicas mais avançadas, é importante compreender como funciona a serialização e desserialização básica com o System.Text.Json. Essa biblioteca oferece suporte nativo para converter objetos C# em JSON e vice-versa. Vejamos o exemplo abaixo:
using System.Text.Json;
public class Pessoa
{
public string Nome { get; set; }
public int Idade { get; set; }
}
var pessoa = new Pessoa { Nome = "Joao", Idade = 30 };
// Serialização: Objeto C# para JSON
string json = JsonSerializer.Serialize(pessoa);
Console.WriteLine(json);
//Resultado apresentado no console: {"Nome":"João","Idade":30}
// Desserialização: JSON para Objeto C#
var desserializado = JsonSerializer.Deserialize(json);
Console.WriteLine($"{desserializado.Nome}, {desserializado.Idade}");
//Resultado apresentado no console: Joao, 30
Neste exemplo, o “System.Text.Json” utiliza automaticamente os nomes das propriedades da classe para mapear os campos no JSON. Esse comportamento é conveniente em cenários onde o formato do JSON corresponde diretamente à estrutura da classe.
Agora que entendemos o funcionamento básico, vamos explorar como lidar com situações mais complexas
Conversores personalizados
Conversores personalizados são úteis quando:
- O formato JSON não corresponde diretamente às propriedades da classe.
- É necessário manipular valores especiais, como enums representados por strings ou formatos de data específicos.
- O JSON contém propriedades dinâmicas ou ausentes.
Criando um conversor personalizado
Os conversores personalizados são implementados estendendo a classe “JsonConverter<T>”. Para usar um conversor, você o registra no nível da propriedade (com [JsonConverter]) ou globalmente, nas opções do serializador.
Imagine um sistema que utiliza o formato de data “dd/MM/yyyy”. Um conversor personalizado pode mapear esse formato para “DateTime”.
{ "Cliente": "Maria", "DataEntrega ": "25/12/1990" }
Para realizarmos a serialização do json em um objeto C#, vamos criar uma classe chamada “Pedido”:
public class Pedido
{
public string Cliente { get; set; }
[JsonConverter(typeof(DateFormatConverter))]
public DateTime DataEntrega { get; set; }
}
A classe “Pedido” possui as propriedades “Cliente” um campo string simples, que será mapeado automaticamente no JSON e “DataEntrega” um campo “DateTime”, que usa o atributo “[JsonConverter]” para associar um conversor personalizado, “DateFormatConverter” que será criado logo abaixo.
Esse atributo é necessário porque o formato padrão de serialização para “DateTime” no “System.Text.Json” usa ISO 8601 (“yyyy-MM-ddTHH:mm:ss.fffZ”), o que não corresponde ao formato específico (“dd/MM/yyyy”) usado neste exemplo.
Agora vamos criar o conversor personalizado:
public class DateFormatConverter : JsonConverter
{
private const string DateFormat = "dd/MM/yyyy";
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.ParseExact(reader.GetString(), DateFormat, null);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(DateFormat));
}
}
O “DateFormatConverter” é uma classe personalizada que estende o comportamento do “System.Text.Json” ao serializar e desserializar datas no formato “dd/MM/yyyy”. Ele herda de “JsonConverter<DateTime>” e implementa dois métodos principais:
Read (Desserialização): Esse método é chamado quando um JSON está sendo convertido em um objeto C#. Ele utiliza o “Utf8JsonReader” para ler o valor da propriedade no JSON e, em seguida, usa “DateTime.ParseExact” para interpretar a string no formato específico (“dd/MM/yyyy”) e transformá-la em um objeto “DateTime”.
Write (Serialização): Durante a serialização, este método é chamado para converter um valor “DateTime” do objeto C# em uma string JSON formatada como “dd/MM/yyyy”. O método utiliza o “Utf8JsonWriter” e chama “value.ToString(DateFormat)” para garantir que o formato de saída corresponda ao esperado.
Para testar podemos utilizar o seguinte código:
var json = @"{ ""Cliente"": ""João"", ""DataEntrega"": ""15/12/2024"" }";
var pedido = JsonSerializer.Deserialize(json);
Console.WriteLine($"{pedido.Cliente} - {pedido.DataEntrega:yyyy-MM-dd}");
Tratamento de Enums como Strings
Enums podem ser representados como strings personalizadas no JSON, e um conversor facilita essa tradução, permitindo que os valores do enum sejam manipulados de maneira mais flexível ao serializar ou desserializar objetos:
public enum Status
{
Ativo,
Inativo
}
public class StatusConverter : JsonConverter
{
public override Status Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.GetString() switch
{
"ativo" => Status.Ativo,
"inativo" => Status.Inativo,
_ => throw new JsonException("Valor inválido")
};
}
public override void Write(Utf8JsonWriter writer, Status value, JsonSerializerOptions options)
{
writer.WriteStringValue(value == Status.Ativo ? "ativo" : "inativo");
}
}
No código acima, o método “Read” verifica qual valor no JSON corresponde aos casos definidos no “switch” e retorna o valor do enum correspondente. Já o método “Write” converte o valor do enum em uma string para ser escrita no JSON.
Agora, vamos criar a classe “Usuario” que irá receber o enum “Status” com o conversor personalizado:
public class Usuario
{
[JsonConverter(typeof(StatusConverter))]
public Status Status { get; set; }
}
Com isso, podemos criar um exemplo para testar o processo de desserialização:
var usuarioJson = @"{ ""Status"": ""ativo"" }";
var usuario = JsonSerializer.Deserialize(usuarioJson);
Console.WriteLine(usuario.Status);
Neste exemplo, o JSON com a string “ativo” é convertido para o valor “Status.Ativo” na propriedade “Status” do objeto “Usuario”. Esse processo permite um mapeamento flexível entre os valores do enum e suas representações em string no JSON, facilitando a integração com sistemas que utilizam formatos específicos para representar enums.
Manipulação de Dados Dinâmicos
Em muitos casos, ao trabalhar com dados JSON, a estrutura pode ser imprevisível ou variar de acordo com diferentes cenários, como quando consumimos APIs externas ou lidamos com sistemas de terceiros. Nesses casos, não é prático ou possível definir previamente classes para representar todos os formatos possíveis de JSON.
Para esses cenários, o “System.Text.Json” fornece ferramentas como “JsonElement” e “JsonDocument”, que permitem acessar e manipular os dados de forma dinâmica, sem a necessidade de modelos específicos.
Para entendermos melhor, imagine que você está consumindo uma API que retorna informações de pedidos, mas o formato do JSON pode variar dependendo dos itens no pedido ou de atualizações no sistema.
var json = @"
{
""PedidoId"": 12345,
""Cliente"": ""João Silva"",
""Itens"": [
{ ""Produto"": ""Notebook"", ""Quantidade"": 1, ""Preco"": 3500.00 },
{ ""Produto"": ""Mouse"", ""Quantidade"": 2, ""Preco"": 150.00 }
],
""Data"": ""2024-12-16"",
""Status"": ""Enviado""
}";
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// Acessando propriedades principais
Console.WriteLine($"Pedido ID: {root.GetProperty("PedidoId").GetInt32()}");
Console.WriteLine($"Cliente: {root.GetProperty("Cliente").GetString()}");
Console.WriteLine($"Data: {root.GetProperty("Data").GetString()}");
Console.WriteLine($"Status: {root.GetProperty("Status").GetString()}");
// Acessando a lista de itens
Console.WriteLine("\nItens do Pedido:");
foreach (var item in root.GetProperty("Itens").EnumerateArray())
{
var produto = item.GetProperty("Produto").GetString();
var quantidade = item.GetProperty("Quantidade").GetInt32();
var preco = item.GetProperty("Preco").GetDecimal();
Console.WriteLine($"- {produto}: {quantidade} x R${preco:F2}");
}
}
Neste cenário, o “JsonDocument.Parse(json)” analisa a string JSON e cria um objeto “JsonDocument” para acessar sua estrutura. Após isso, o “GetProperty()” é usado para acessar valores como “PedidoId”, “Cliente”, “Data” e “Status”. Esses métodos retornam um “JsonElement”, do qual podemos extrair valores usando métodos como “GetString()”, “GetInt32()” e “GetDecimal()”.
O resultado que teremos no console será:
Pedido ID: 12345
Cliente: João Silva
Data: 2024-12-16
Status: Enviado
Itens do Pedido:
- Notebook: 1 x R$3500,00
- Mouse: 2 x R$150,00
Acelere a sua carreira conosco!
Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui
Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui
Conclusão
O “System.Text.Json” oferece flexibilidade e um ótimo desempenho para manipular JSON em aplicações .NET. Com conversores personalizados, você pode adaptar o processamento de JSON às necessidades mais complexas, garantindo que seus sistemas permaneçam robustos e eficientes. Experimente as técnicas apresentadas neste artigo para dominar a manipulação avançada de JSON!