1. Introdução
Por muito tempo, no ecossistema .NET a principal ferramenta para trabalhar com JSON foi o ótimo pacote de terceiros Newtonsoft.Json (também conhecido como Json.NET). Ele é poderoso, flexível e ainda é amplamente usado. Mas com a chegada das novas versões .NET 9 e C# 14, a Microsoft decidiu que era hora de ter um serializador JSON embutido e de alto desempenho. Assim surgiu o System.Text.Json.
Por que uma nova abordagem? O System.Text.Json foi projetado com as realidades modernas em mente e resolve problemas que se acumularam ao longo dos anos usando bibliotecas de terceiros. Ele é otimizado para máxima velocidade e segurança, é ideal para cenários assíncronos e web APIs, e — o melhor — não precisa ser instalado via NuGet: já vem na plataforma.
Claro, o Newtonsoft.Json não desapareceu, e vamos ver ele depois. Mas para a maioria dos projetos novos o System.Text.Json é a escolha padrão. Prepare-se, agora vamos ensinar nossos objetos a falar a língua JSON!
2. Fundamentos do trabalho com System.Text.Json
Serialização simples de um objeto
Então, vamos começar com a mágica básica. Vamos serializar nosso objeto para uma string JSON.
using System;
using System.Text.Json; // Não esqueça de adicionar!
public class Player
{
public string Name { get; set; }
public int Health { get; set; }
public bool IsAlive { get; set; }
}
// Em algum lugar do seu programa:
Player player = new Player { Name = "Aragorn", Health = 100, IsAlive = true };
// Serialização:
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // Vai imprimir: {"Name":"Aragorn","Health":100,"IsAlive":true}
Comentário: Se você acabou de começar a estudar serialização, esse exemplo mostra como tudo é simples: JsonSerializer.Serialize — e pronto.
Desserialização de um objeto
Vamos reviver um objeto a partir de uma string JSON:
string incomingJson = "{\"Name\":\"Legolas\",\"Health\":88,\"IsAlive\":true}";
Player player2 = JsonSerializer.Deserialize<Player>(incomingJson);
Console.WriteLine(player2.Name); // Legolas
Console.WriteLine(player2.Health); // 88
Console.WriteLine(player2.IsAlive); // true
Comentário: Se a estrutura do JSON coincide com a sua classe — tudo funciona direitinho. Se não — podem ocorrer exceções ou valores padrão.
3. Princípios de funcionamento e arquitetura da serialização
Como acontece o mapeamento
O System.Text.Json por padrão usa os mesmos nomes das propriedades que estão na classe. Case-sensitive! Se no JSON estiver escrito health em vez de Health, a desserialização não vai funcionar — a propriedade ficará com o valor padrão (0, false ou null).
Por exemplo:
// JSON com chaves em minúsculas:
string badJson = "{\"name\":\"Gimli\",\"health\":120,\"isAlive\":true}";
Player player3 = JsonSerializer.Deserialize<Player>(badJson);
Console.WriteLine(player3.Name); // vazio
Console.WriteLine(player3.Health); // 0
Console.WriteLine(player3.IsAlive); // false
Curiosidade: Muitos APIs usam chaves em camelCase (health), enquanto em C# é comum usar PascalCase (Health). Isso é resolvido com configurações (veja abaixo).
4. Controle da serialização — opções e configurações
Formatando o JSON: saída "legível por humanos"
Às vezes você quer não um JSON compacto, mas um JSON bonito — para configs ou logs.
var options = new JsonSerializerOptions
{
WriteIndented = true // Adicionar indentação
};
string prettyJson = JsonSerializer.Serialize(player, options);
Console.WriteLine(prettyJson);
/*
{
"Name": "Frodo",
"Health": 50,
"IsAlive": true,
"Inventory": [
"Ring",
"Bread",
"Torch"
],
"Position": {
"X": 5,
"Y": 15
}
}
*/
A propriedade WriteIndented é usada, e o serializador adiciona indentação e quebras de linha.
Controlando o estilo de nomes (CamelCase vs PascalCase)
Se você está trabalhando com uma web API onde todas as chaves são camelCase, ative a política de nomes:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string camelCaseJson = JsonSerializer.Serialize(player, options);
// {"name":"Frodo","health":50,"isAlive":true,"inventory":["Ring","Bread","Torch"],"position":{"x":5,"y":15}}
Assim, na desserialização essas chaves serão mapeadas corretamente:
string apiJson = "{\"name\":\"Bilbo\",\"health\":40,\"isAlive\":true,\"inventory\":[\"Mug\"],\"position\":{\"x\":10,\"y\":5}}";
Player bilbo = JsonSerializer.Deserialize<Player>(apiJson, options);
Console.WriteLine(bilbo.Name); // Bilbo
5. Dicas úteis
Usando o atributo [JsonIgnore]
Às vezes nem todas as propriedades devem ser serializadas — por exemplo, dados privados ou valores temporários calculados.
using System.Text.Json.Serialization;
public class Player
{
public string Name { get; set; }
public int Health { get; set; }
[JsonIgnore] // Essa propriedade não vai para o JSON
public bool IsSecretCharacter { get; set; }
}
Agora, ao serializar:
var player = new Player { Name = "Boromir", Health = 80, IsSecretCharacter = true };
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // {"Name":"Boromir","Health":80}
Ao desserializar de volta, IsSecretCharacter receberá o valor padrão (false).
Usando [JsonPropertyName("...")]
Suponha que no código a propriedade se chame IsAlive, mas no JSON ela deve ser "status":
using System.Text.Json.Serialization;
public class Player
{
public string Name { get; set; }
public int Health { get; set; }
[JsonPropertyName("status")]
public bool IsAlive { get; set; }
}
A serialização agora ficará assim:
var player = new Player { Name = "Pippin", Health = 60, IsAlive = false };
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // {"Name":"Pippin","Health":60,"status":false}
E ao desserializar, a chave "status" vai preencher corretamente a propriedade IsAlive.
Limitações embutidas e características de segurança
- Por padrão só são serializadas propriedades públicas com getters/setters; campos/propriedades privados são ignorados.
- Em caso de referências cíclicas é lançada uma exceção: "A possible object cycle was detected".
- Diferente do Newtonsoft.Json, o serializador padrão depende menos de "truques" mágicos com tipos — em contrapartida é mais seguro e mais rápido para cenários típicos.
6. Erros comuns e armadilhas
Você muda o nome de uma propriedade no JSON e esquece de atualizar o código — como resultado a propriedade recebe o valor padrão (null, 0, false).
O campo necessário está ausente no JSON — a propriedade correspondente do objeto ficará com o valor default (veja a documentação).
Uma propriedade não tem um setter público — na desserialização ela não será preenchida.
Você mudou a estrutura de classes aninhadas ou coleções — a desserialização pode quebrar ou produzir resultados inesperados.
Nomes iguais em níveis diferentes de aninhamento (no pai e no objeto filho) confundem e dificultam o debug.
Às vezes a causa é a versão da plataforma: versões antigas do System.Text.Json tinham suporte pior para alguns tipos (por exemplo, Dictionary, DateTime, enum), mas em .NET 7/8/9 muita coisa foi corrigida.
GO TO FULL VERSION