C# JSON
Gravar texto num arquivo você já sabe. Mas como gravar um objeto, um contato com nome, cidade e telefone, ou uma lista com cem deles, e depois ler de volta exatamente igual? O formato mais usado do mundo para isso é o JSON. É nele que os sites conversam com os servidores, que os aplicativos guardam configurações e que muitas lojas trocam dados. Nesta aula você vai usar o System.Text.Json, que já vem no .NET, para transformar objetos em JSON e JSON em objetos, e vai salvar a agenda de contatos da Ana num arquivo.
- Ler e escrever o formato JSON: objetos, listas, textos, números,
true,falseenull. - Transformar um objeto em JSON com
JsonSerializer.Serialize(serializar), bonito comWriteIndented. - Transformar JSON em objeto com
JsonSerializer.Deserialize<T>(desserializar). - Salvar e carregar uma lista num arquivo
.json. - Controlar os nomes:
camelCase,PropertyNameCaseInsensitive,[JsonPropertyName]e[JsonIgnore]. - Ler um JSON sem criar classe, com
JsonNode. - Os erros:
JsonException, nomes que não batem e a reflexão desativada nodotnet run arquivo.cs.
Esta aula usa records, propriedades, List<T>, generics (o <T>) e a aula Arquivos.
No .NET 10, o dotnet run arquivo.cs (de um arquivo só) vem preparado para virar um programa "nativo" (AOT, seção "Quando dá errado"), e nesse modo o JsonSerializer simples é desligado. Por isso, os exemplos desta aula rodam num projeto, criado com dotnet new console -o Agenda (aula Projetos). O código vai no Program.cs e roda com dotnet run. Nas caixas "Experimente", o record fica numa aba à parte, para a caixa também funcionar como um projeto. (O editor do guia segue a mesma regra do dotnet: uma caixa com um .cs só é um arquivo solto e dá o mesmo erro; com duas abas .cs, é um projeto.)
$ dotnet new console -o Agenda O modelo "Aplicativo do Console" foi criado com êxito. Processando ações pós-criação... Restaurando /home/ana/projetos/Agenda/Agenda.csproj: Determinando os projetos a serem restaurados... /home/ana/projetos/Agenda/Agenda.csproj restaurado (em 108 ms). A restauração foi bem-sucedida. $ cd Agenda
A ideia: a ficha que qualquer um preenche
Um objeto vive na memória do seu programa, num formato que só o .NET entende. Para guardá-lo num arquivo, ou mandá-lo para outro programa (que pode nem ser em C#), você precisa escrevê-lo numa ficha padronizada, que qualquer um sabe ler e preencher. O JSON (JavaScript Object Notation, "notação de objetos do JavaScript") é essa ficha. Ele é só texto:
{
"Nome": "Bruno",
"Cidade": "Curitiba",
"Idade": 34,
"Cliente": true,
"Apelido": null,
"Telefones": ["(41) 99999-0000", "(41) 3333-0000"]
}
| No JSON | Exemplo | Em C# |
|---|---|---|
Objeto: pares "nome": valor entre { }, separados por vírgula | { "Nome": "Bruno" } | Um objeto de uma classe ou record |
| Texto, sempre entre aspas duplas | "Curitiba" | string |
| Número, com ponto nos decimais, em qualquer país | 34, 15.90 | int, double, decimal… |
| Verdadeiro ou falso | true, false | bool |
| Nada | null | null |
Lista (array) entre [ ] | ["a", "b"] | array, List<T> |
Transformar um objeto em JSON se chama serializar (pôr "em série", numa fila de letras). O caminho de volta, de JSON para objeto, é desserializar. Os dois ficam na classe JsonSerializer, do namespace System.Text.Json, que precisa de using.
1. Objeto → JSON: Serialize
using System.Text.Json;
Contato ana = new Contato("Ana Souza", "ana@exemplo.com", 29, ["Recife", "São Paulo"]);
string json = JsonSerializer.Serialize(ana);
Console.WriteLine(json);
JsonSerializerOptions opcoes = new JsonSerializerOptions { WriteIndented = true };
Console.WriteLine(JsonSerializer.Serialize(ana, opcoes));
record Contato(string Nome, string Email, int Idade, List<string> Cidades);
$ dotnet run
{"Nome":"Ana Souza","Email":"ana@exemplo.com","Idade":29,"Cidades":["Recife","São Paulo"]}
{
"Nome": "Ana Souza",
"Email": "ana@exemplo.com",
"Idade": 29,
"Cidades": [
"Recife",
"São Paulo"
]
}
- using System.Text.Json;
- Traz o
JsonSerializere oJsonSerializerOptions. Esse namespace não vem incluído sozinho. - record Contato(…)
- Um record com quatro propriedades. Fica no fim do arquivo, depois das instruções do programa. Uma classe com propriedades
{ get; set; }funciona do mesmo jeito. - JsonSerializer.Serialize(ana)
- Olha as propriedades públicas do objeto e escreve cada uma como
"Nome": valor. A lista virou um array JSON. O resultado é umastringcomum, que você pode mostrar, gravar num arquivo ou mandar pela internet. - new JsonSerializerOptions { WriteIndented = true }
- As opções ("configurações") do serializador.
WriteIndented("escrever com recuo") põe cada valor numa linha, com recuo: mais fácil para pessoas lerem. Sem ele, tudo sai numa linha só, menor, melhor para programas. - São Paulo
- O
ãsaiu comoã, o código Unicode da letra. É JSON válido, e quem ler o arquivo recebeSão Paulode volta. A seção 4 mostra como deixar o acento visível.
2. JSON → objeto: Deserialize
O caminho de volta. O Deserialize precisa saber em que tipo transformar o texto, e você diz isso entre < >:
using System.Text.Json;
string json = File.ReadAllText("contato.json");
Contato? c = JsonSerializer.Deserialize<Contato>(json);
if (c != null)
{
Console.WriteLine($"{c.Nome} mora em {c.Cidade}");
}
record Contato(string Nome, string Cidade, string Telefone);
$ cat contato.json
{
"Nome": "Bruno",
"Cidade": "Curitiba",
"Telefone": "(41) 99999-0000"
}
$ dotnet run
Bruno mora em Curitiba
- JsonSerializer.Deserialize<Contato>(json)
- Lê o texto e cria um
Contato, preenchendo cada propriedade com o valor do mesmo nome no JSON. - Contato?
- O resultado pode ser
null: se o texto for literalmentenull, é isso que volta. Por isso a variável éContato?e oif (c != null)confere antes de usar (aula Referências Anuláveis).
O JSON é lido com a mesma regra de nomes do Serialize: "Nome" no texto vai para Nome no objeto. Valores do JSON que não existem no record são ignorados, e propriedades do record que não aparecem no JSON ficam com o valor padrão (null, 0, false).
Missão: o arquivo contato.json (na outra aba) tem a ficha da Carla. Transforme o texto num Contato com JsonSerializer.Deserialize<Contato>(json) e mostre Carla mora em Salvador.
using System.Text.Json;
string json = File.ReadAllText("contato.json");
Console.WriteLine(json);
// transforme o json num Contato e mostre "Nome mora em Cidade"
record Contato(string Nome, string Cidade, string Telefone);
{
"Nome": "Carla",
"Cidade": "Salvador",
"Telefone": "(71) 97777-2222"
}
using System.Text.Json;
string json = File.ReadAllText("contato.json");
Contato? c = JsonSerializer.Deserialize<Contato>(json);
if (c != null)
{
Console.WriteLine($"{c.Nome} mora em {c.Cidade}");
}
Nas caixas desta aula, o record fica numa aba própria, Contato.cs. Com dois arquivos .cs, a caixa funciona como um projeto, igual ao ~/projetos/Agenda da Ana (no projeto, ele também pode ficar num arquivo separado; todos os .cs da pasta são compilados juntos).
3. Salvar e carregar uma lista
A agenda da Ana é uma List<Contato>. O JSON de uma lista é um array de objetos, e o Deserialize aceita o tipo List<Contato> do mesmo jeito. Juntando com o File, a agenda passa a sobreviver ao fim do programa:
using System.Text.Json;
JsonSerializerOptions opcoes = new JsonSerializerOptions { WriteIndented = true };
string texto = File.ReadAllText("contatos.json");
List<Contato> agenda = JsonSerializer.Deserialize<List<Contato>>(texto) ?? [];
agenda.Add(new Contato("Carla", "Salvador", "(71) 97777-2222"));
File.WriteAllText("contatos.json", JsonSerializer.Serialize(agenda, opcoes));
Console.WriteLine($"{agenda.Count} contatos");
Console.WriteLine(File.ReadAllText("contatos.json"));
record Contato(string Nome, string Cidade, string Telefone);
$ dotnet run
3 contatos
[
{
"Nome": "Ana",
"Cidade": "Recife",
"Telefone": "(81) 98888-1111"
},
{
"Nome": "Bruno",
"Cidade": "Curitiba",
"Telefone": "(41) 99999-0000"
},
{
"Nome": "Carla",
"Cidade": "Salvador",
"Telefone": "(71) 97777-2222"
}
]
- Deserialize<List<Contato>>(texto)
- O tipo agora é "lista de contatos". O array do arquivo, que tinha a Ana e o Bruno, virou uma lista com dois itens.
- ?? []
- Se o resultado for
null(o arquivo tinha sónull), use uma lista vazia (operador??, aula Operadores ??, ?. e ??=). AssimagendaéList<Contato>, sem?, e o resto do programa não precisa conferir. - File.WriteAllText("contatos.json", JsonSerializer.Serialize(agenda, opcoes))
- Serializa a lista inteira e substitui o arquivo, já com a Carla.
Esse é o jeito mais simples de um programa pequeno "lembrar" das coisas: carregar tudo no começo, mexer na lista e salvar tudo no fim. O projeto Agenda de Contatos faz um programa completo assim.
4. Nomes, acentos e números
Muitos sites e programas escrevem JSON em camelCase ("nome", "precoUnitario"), e o C# usa PascalCase nas propriedades (Nome, PrecoUnitario). As opções e dois atributos resolvem a diferença:
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
JsonSerializerOptions opcoes = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
List<Produto> estoque =
[
new Produto { Nome = "Caderno", Preco = 15.90m, Quantidade = 12 },
new Produto { Nome = "Caneta azul", Preco = 2.50m, Quantidade = 40, Senha = "x" },
];
Console.WriteLine(JsonSerializer.Serialize(estoque, opcoes));
class Produto
{
public string Nome { get; set; } = "";
public decimal Preco { get; set; }
[JsonPropertyName("qtd")]
public int Quantidade { get; set; }
[JsonIgnore]
public string? Senha { get; set; }
}
$ dotnet run
[
{
"nome": "Caderno",
"preco": 15.90,
"qtd": 12
},
{
"nome": "Caneta azul",
"preco": 2.50,
"qtd": 40
}
]
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase
- "Política de nomes": escreve
Nomecomo"nome"ePrecocomo"preco". Use as mesmas opções noDeserialize, para a volta entender os nomes. - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
- Deixa acentos como
ãe símbolos como&sem virarã. O "Unsafe" ("inseguro") é porque esse JSON não deve ser colado dentro de uma página HTML sem cuidado; para arquivos e programas, é tranquilo. Fica emSystem.Text.Encodings.Web. - [JsonPropertyName("qtd")]
- Um atributo (de
System.Text.Json.Serialization) que escolhe o nome exato no JSON só para essa propriedade. Vale nos dois sentidos. - [JsonIgnore]
- "Ignorar": a propriedade não vai para o JSON (nem é lida dele). Bom para senhas e valores calculados.
- "preco": 15.90
- No JSON, o número sai sempre com ponto, mesmo no computador em pt-BR da Ana. O JSON não depende da cultura: não há o problema do
8.5que vira85. E odecimalmanteve os dois zeros.
Ler sem diferenciar maiúsculas
Ao ler um JSON escrito por outro programa, você nem sempre sabe se os nomes vêm como "nome", "Nome" ou "NOME". A opção PropertyNameCaseInsensitive = true ("nomes sem diferenciar maiúsculas") aceita todos. Este trecho usa a mesma classe Produto (no fim do arquivo, como antes):
string deFora = """{"nome":"Régua","preco":1.2,"cor":"azul"}""";
Produto? r1 = JsonSerializer.Deserialize<Produto>(deFora);
Console.WriteLine($"Sem opções: [{r1?.Nome}] {r1?.Preco}");
JsonSerializerOptions flexivel = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
Produto? r2 = JsonSerializer.Deserialize<Produto>(deFora, flexivel);
Console.WriteLine($"Sem diferenciar maiúsculas: [{r2?.Nome}] {r2?.Preco}");
$ dotnet run Sem opções: [] 0 Sem diferenciar maiúsculas: [Régua] 1,2
Sem a opção, nenhum nome bateu ("nome" não é Nome) e o produto veio vazio, sem nenhum erro. Com ela, deu certo. O "cor", que não existe na classe, foi ignorado nos dois casos. O """…""" é a string literal crua da aula Escapes, que deixa escrever aspas sem barra.
5. Ler sem criar classe: JsonNode
Às vezes você só quer um ou dois valores de um JSON grande, e criar classes para tudo dá trabalho. O JsonNode ("nó JSON", de System.Text.Json.Nodes) lê o JSON como uma árvore, que você percorre com ["nome"] e [0]:
using System.Text.Json.Nodes;
string json = """
{
"loja": "Papelaria da Ana",
"aberta": true,
"produtos": [
{ "nome": "Caderno", "preco": 15.90 },
{ "nome": "Caneta", "preco": 2.50 }
]
}
""";
JsonNode? raiz = JsonNode.Parse(json);
Console.WriteLine(raiz?["loja"]);
Console.WriteLine((bool?)raiz?["aberta"]);
JsonArray produtos = raiz!["produtos"]!.AsArray();
Console.WriteLine($"{produtos.Count} produtos");
foreach (JsonNode? p in produtos)
{
string nome = (string)p!["nome"]!;
decimal preco = (decimal)p["preco"]!;
Console.WriteLine($"{nome}: {preco:C}");
}
$ dotnet run Papelaria da Ana True 2 produtos Caderno: R$ 15,90 Caneta: R$ 2,50
- JsonNode.Parse(json)
- "Analisa" o texto e devolve a raiz da árvore (ou
null, se o texto fornull). - raiz?["loja"]
- O valor do campo
"loja". Se o campo não existir, vemnull, sem exceção. - (bool?)…, (string)…, (decimal)…
- Um cast converte o nó no tipo C# que você quer.
- .AsArray()
- Trata o nó como lista, com
Counteforeach. - ! (depois de um valor)
- O operador "eu garanto que não é
null" (aula Referências Anuláveis). Aqui você sabe que os campos existem; num JSON de fora, prefira conferir.
Qual usar
| Você quer… | Use |
|---|---|
| Guardar e carregar os seus próprios objetos | JsonSerializer.Serialize e Deserialize<T> com um record ou classe |
| Um arquivo que pessoas vão ler | WriteIndented = true e o Encoder que mantém os acentos |
Conversar com um site que usa "nomeAssim" | PropertyNamingPolicy = JsonNamingPolicy.CamelCase (nos dois sentidos) |
| Ler JSON de fora, sem saber as maiúsculas | PropertyNameCaseInsensitive = true |
| Um nome especial numa propriedade | [JsonPropertyName("…")] |
| Pegar alguns valores de um JSON grande | JsonNode.Parse |
Programa nativo (AOT) ou dotnet run arquivo.cs | Geração de código com JsonSerializerContext (abaixo) |
Quando dá errado
Reflection-based serialization has been disabled no dotnet run arquivo.cs
A Ana tentou o primeiro exemplo da aula num arquivo solto, ~/cs/json1.cs, como nas aulas dos níveis 1 e 2:
$ dotnet run json1.cs /home/ana/cs/json1.cs(4,15): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. /home/ana/cs/json1.cs(4,15): warning IL3050: Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications. … « o mesmo par de avisos para cada Serialize e Deserialize Unhandled exception. System.InvalidOperationException: Reflection-based serialization has been disabled for this application. Either use the source generator APIs or explicitly configure the 'JsonSerializerOptions.TypeInfoResolver' property. at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_JsonSerializerIsReflectionDisabled() … « mais 4 linhas de dentro do .NET at Program.<Main>$(String[] args) in /home/ana/cs/json1.cs:line 4
O que aconteceu: no .NET 10, um programa de arquivo único vem configurado para ser publicado como programa nativo (AOT, "compilado antes", aula Publicar o Programa). Um programa nativo não pode examinar as classes enquanto roda (isso se chama reflexão), e é assim que o JsonSerializer simples descobre as propriedades. Os avisos IL2026 e IL3050 avisam disso, e a exceção diz "a serialização por reflexão foi desativada para este programa". Num projeto (dotnet new console), essa configuração vem desligada, e tudo funciona.
Como resolver: três caminhos.
- Use um projeto, como nesta aula.
- No arquivo solto, desligue o modo nativo com uma diretiva na primeira linha:
#:property PublishAot=false. Os avisos somem e o programa roda. - Use a geração de código, que funciona em todo lugar, inclusive em programas nativos: uma classe
partialmarcada com[JsonSerializable], que o compilador completa sozinho.
#:property PublishAot=false
using System.Text.Json;
Contato ana = new Contato("Ana Souza", "ana@exemplo.com", 29, ["Recife", "São Paulo"]);
Console.WriteLine(JsonSerializer.Serialize(ana));
record Contato(string Nome, string Email, int Idade, List<string> Cidades);
using System.Text.Json;
using System.Text.Json.Serialization;
Contato ana = new Contato("Ana Souza", "ana@exemplo.com", 29, ["Recife", "São Paulo"]);
string json = JsonSerializer.Serialize(ana, AgendaJson.Default.Contato);
Console.WriteLine(json);
Contato? volta = JsonSerializer.Deserialize(json, AgendaJson.Default.Contato);
Console.WriteLine(volta?.Nome);
record Contato(string Nome, string Email, int Idade, List<string> Cidades);
[JsonSerializable(typeof(Contato))]
partial class AgendaJson : JsonSerializerContext { }
$ dotnet run json2.cs
{"Nome":"Ana Souza","Email":"ana@exemplo.com","Idade":29,"Cidades":["Recife","São Paulo"]}
$ dotnet run json3.cs
{"Nome":"Ana Souza","Email":"ana@exemplo.com","Idade":29,"Cidades":["Recife","São Paulo"]}
Ana Souza
No json3.cs, o AgendaJson.Default.Contato é a "receita" para ler e escrever um Contato, que o compilador gerou ao ver o [JsonSerializable(typeof(Contato))]. Para uma lista, acrescente outro atributo: [JsonSerializable(typeof(List<Contato>))].
O editor do guia faz igual ao terminal: o json1.cs numa caixa de um arquivo só dá os mesmos avisos IL2026/IL3050 e a mesma exceção, e o json2.cs e o json3.cs funcionam. A geração de código é o caminho que funciona em todo lugar: arquivo solto, projeto, programa nativo e editor. Experimente:
Missão: esta caixa tem um arquivo só, como o dotnet run lista.cs, e usa a geração de código. Mas o AgendaJson só sabe escrever um Contato, e o programa quer escrever a lista inteira: rode e veja o erro CS1061 (não existe ListContato). Acrescente, logo acima do partial class, o atributo [JsonSerializable(typeof(List<Contato>))] e rode de novo: o compilador cria a receita AgendaJson.Default.ListContato.
using System.Text.Json;
using System.Text.Json.Serialization;
List<Contato> agenda = [new("Ana", "Recife"), new("Bruno", "Natal")];
string json = JsonSerializer.Serialize(agenda, AgendaJson.Default.ListContato);
Console.WriteLine(json);
record Contato(string Nome, string Cidade);
[JsonSerializable(typeof(Contato))]
partial class AgendaJson : JsonSerializerContext { }
using System.Text.Json;
using System.Text.Json.Serialization;
List<Contato> agenda = [new("Ana", "Recife"), new("Bruno", "Natal")];
string json = JsonSerializer.Serialize(agenda, AgendaJson.Default.ListContato);
Console.WriteLine(json);
record Contato(string Nome, string Cidade);
[JsonSerializable(typeof(Contato))]
[JsonSerializable(typeof(List<Contato>))]
partial class AgendaJson : JsonSerializerContext { }
JsonException: o valor não é do tipo certo
using System.Text.Json;
string json = """{"Nome":"Ana", "Idade": "vinte e nove"}""";
Pessoa? p = JsonSerializer.Deserialize<Pessoa>(json);
Console.WriteLine(p?.Nome);
record Pessoa(string Nome, int Idade);
$ dotnet run Unhandled exception. System.Text.Json.JsonException: The JSON value could not be converted to Pessoa. Path: $.Idade | LineNumber: 0 | BytePositionInLine: 38. ---> System.InvalidOperationException: Cannot get the value of a token type 'String' as a number. at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ExpectedNumber(JsonTokenType tokenType) … « mais 14 linhas de dentro do .NET at Program.<Main>$(String[] args) in /home/ana/projetos/Agenda/Program.cs:line 4
O que aconteceu: "O valor JSON não pôde ser convertido". A Idade é int, mas no JSON veio um texto (entre aspas). A mensagem é ótima para achar o problema: Path: $.Idade é o campo ($ é a raiz), e LineNumber e BytePositionInLine dizem a linha e a posição no texto, contando do zero.
Como resolver: conserte o JSON ("Idade": 29, sem aspas) ou, se o JSON vem de fora, trate com try { … } catch (JsonException e) { Console.WriteLine(e.Message); }.
JsonException: JSON mal escrito
$ grep json Program.cs
string json = """{"Nome":"Ana", "Idade": 29,}""";
$ dotnet run
Unhandled exception. System.Text.Json.JsonException: The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. Path: $ | LineNumber: 0 | BytePositionInLine: 27.
---> System.Text.Json.JsonReaderException: The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. LineNumber: 0 | BytePositionInLine: 27.
… « mais 14 linhas de dentro do .NET
at Program.<Main>$(String[] args) in /home/ana/projetos/Agenda/Program.cs:line 4
O que aconteceu: "O objeto JSON tem uma vírgula sobrando no fim". O JSON é mais rígido que o C#: nada de vírgula depois do último item, nada de comentários, nada de aspas simples ('Ana'), e os nomes sempre entre aspas duplas.
Como resolver: tire a vírgula. Se o JSON vem de um lugar que escreve assim, as opções AllowTrailingCommas = true e ReadCommentHandling = JsonCommentHandling.Skip deixam o leitor mais tolerante.
using System.Text.Json;
Pessoa? p = JsonSerializer.Deserialize<Pessoa>("""{"nome":"Ana","idade":29}""");
Console.WriteLine($"{p?.Nome} tem {p?.Idade} anos");
record Pessoa(string Nome, int Idade);
$ dotnet run tem 0 anos
O que aconteceu: o JSON usa "nome" e "idade", minúsculos, e o record usa Nome e Idade. Por padrão, o Deserialize diferencia maiúsculas, não achou nenhum campo e deixou os valores padrão: null e 0. Campos que não batem nunca dão erro.
Como resolver: passe new JsonSerializerOptions { PropertyNameCaseInsensitive = true }, ou PropertyNamingPolicy = JsonNamingPolicy.CamelCase (seção 4). Para exigir um campo, marque a propriedade com [JsonRequired]: aí a falta dele lança JsonException.
CS8600 e CS8602: o resultado pode ser null
$ grep Deserialize Program.cs
Pessoa p = JsonSerializer.Deserialize<Pessoa>("""{"Nome":"Ana","Idade":29}""");
$ dotnet run
/home/ana/projetos/Agenda/Program.cs(3,12): warning CS8600: Conversão de literal nula ou possível valor nulo em tipo não anulável. [/home/ana/projetos/Agenda/Agenda.csproj]
/home/ana/projetos/Agenda/Program.cs(4,22): warning CS8602: Desreferência de uma referência possivelmente nula. [/home/ana/projetos/Agenda/Agenda.csproj]
Ana tem 29 anos
O que aconteceu: o Deserialize devolve Pessoa?, e a variável foi declarada como Pessoa. Num projeto, o aviso vem com o nome do .csproj entre colchetes no fim.
Como resolver: Pessoa? p = … e confira com if (p != null), ou dê um valor para o caso null com ??, como em ?? [] para listas.
Referência rápida
| Código | O que faz |
|---|---|
using System.Text.Json; | Traz JsonSerializer e JsonSerializerOptions |
JsonSerializer.Serialize(obj) | Objeto → texto JSON |
JsonSerializer.Serialize(obj, opcoes) | Com opções |
JsonSerializer.Deserialize<T>(json) | Texto JSON → objeto (T?) |
new JsonSerializerOptions { WriteIndented = true } | Uma linha por valor, com recuo |
PropertyNamingPolicy = JsonNamingPolicy.CamelCase | Nomes "assim" |
PropertyNameCaseInsensitive = true | Ler sem diferenciar maiúsculas |
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping | Acentos visíveis (System.Text.Encodings.Web) |
[JsonPropertyName("x")], [JsonIgnore], [JsonRequired] | Nome próprio, fora do JSON, obrigatório (System.Text.Json.Serialization) |
JsonNode.Parse(json)?["campo"] | Ler sem classe (System.Text.Json.Nodes) |
[JsonSerializable(typeof(T))] partial class X : JsonSerializerContext { } | Geração de código, para AOT e arquivos soltos |
#:property PublishAot=false | No dotnet run arquivo.cs, liga de novo a reflexão |
Exercícios
1. Para JSON
Complete para transformar o objeto produto num texto JSON:
2. De volta
Complete para transformar o texto numa lista de produtos:
3. Bonito para ler
Complete a opção que põe cada valor numa linha, com recuo:
4. Num computador em pt-BR, como o JsonSerializer escreve o decimal 2.50m?
5. O JSON tem "nome": "Ana" e a classe tem Nome. Sem opções, o que o Deserialize faz?
6. Qual destes textos é um JSON válido?
Missão final: a agenda que lembra
Missão: o programa carrega a agenda do contatos.json (na outra aba) e mostra quantos contatos ela tem. Complete-o:
- acrescente na lista o contato
new Contato("Carla", "Salvador", "(71) 97777-2222"); - serialize a lista com
WriteIndented = truee grave emcontatos.jsoncomFile.WriteAllText; - mostre a quantidade (
3 contatos) e depois o arquivo, lido de volta comFile.ReadAllText.
using System.Text.Json;
string texto = File.ReadAllText("contatos.json");
List<Contato> agenda = JsonSerializer.Deserialize<List<Contato>>(texto) ?? [];
// 1. acrescente a Carla
// 2. grave a lista, com recuo, em contatos.json
Console.WriteLine($"{agenda.Count} contatos");
// 3. mostre o arquivo
record Contato(string Nome, string Cidade, string Telefone);
[
{
"Nome": "Ana",
"Cidade": "Recife",
"Telefone": "(81) 98888-1111"
},
{
"Nome": "Bruno",
"Cidade": "Curitiba",
"Telefone": "(41) 99999-0000"
}
]
using System.Text.Json;
string texto = File.ReadAllText("contatos.json");
List<Contato> agenda = JsonSerializer.Deserialize<List<Contato>>(texto) ?? [];
agenda.Add(new Contato("Carla", "Salvador", "(71) 97777-2222"));
JsonSerializerOptions opcoes = new JsonSerializerOptions { WriteIndented = true };
File.WriteAllText("contatos.json", JsonSerializer.Serialize(agenda, opcoes));
Console.WriteLine($"{agenda.Count} contatos");
Console.WriteLine(File.ReadAllText("contatos.json"));
No seu computador, rodando o programa de novo, a agenda carrega os 3 contatos do arquivo e grava 4: ela "lembra" entre uma execução e outra. Se quiser ir além: antes de acrescentar, confira se já existe alguém com o mesmo nome (agenda.Any(c => c.Nome == "Carla"), aula LINQ).
Resumo
- JSON é texto: objetos
{ "nome": valor }, listas[ ], textos entre aspas duplas, números com ponto,true,falseenull. JsonSerializer.Serialize(obj)transforma as propriedades públicas em JSON;Deserialize<T>(json)faz o caminho de volta e devolveT?.- Para salvar e carregar:
File.WriteAllText(c, JsonSerializer.Serialize(lista, opcoes))eJsonSerializer.Deserialize<List<T>>(File.ReadAllText(c)) ?? []. - As opções controlam o formato (
WriteIndented), os nomes (CamelCase,PropertyNameCaseInsensitive) e os acentos (Encoder). Use as mesmas na ida e na volta. - Nomes que não batem deixam valores vazios, sem erro. JSON mal escrito ou com tipo errado lança
JsonException, com o campo e a posição. - No
dotnet run arquivo.cs, a serialização por reflexão vem desligada: use um projeto,#:property PublishAot=falseou a geração de código comJsonSerializerContext.