JSON está em absolutamente tudo. É o formato de dados mais usado na web — em APIs, arquivos de configuração, bancos de dados NoSQL, comunicação entre serviços, LocalStorage e muito mais. Você já o usou nos artigos anteriores, mas de forma superficial.
Neste artigo vamos explorar o JSON com profundidade — suas regras, seus limites, casos especiais, técnicas avançadas e os erros mais comuns que desenvolvedores cometem ao trabalhar com ele.
O que é JSON?
JSON — JavaScript Object Notation — é um formato de texto para representar dados estruturados. Criado por Douglas Crockford no início dos anos 2000, foi inspirado na sintaxe de objetos do JavaScript mas é independente de linguagem — praticamente toda linguagem de programação moderna sabe ler e escrever JSON.
{
"nome": "Ana Paula",
"idade": 28,
"ativo": true,
"endereco": {
"rua": "Av. Paulista",
"numero": 1000,
"cidade": "São Paulo"
},
"habilidades": ["JavaScript", "React", "Node.js"],
"foto": null
}
Regras do JSON — diferenças do objeto JavaScript
JSON parece um objeto JavaScript, mas tem regras mais rígidas:
// ── Objeto JavaScript ──────────────────────────────
const obj = {
nome: "Ana", // chave sem aspas — válido em JS
'idade': 28, // aspas simples — válido em JS
ativo: true,
saudar() { // métodos — válido em JS
return "olá";
},
indefinido: undefined, // undefined — válido em JS
};
// ── JSON válido ────────────────────────────────────
// ✅ Chaves SEMPRE entre aspas duplas
// ✅ Strings SEMPRE entre aspas duplas
// ❌ Sem métodos (funções)
// ❌ Sem undefined
// ❌ Sem comentários
// ❌ Sem trailing comma (vírgula no último item)
const jsonValido = `{
"nome": "Ana",
"idade": 28,
"ativo": true,
"endereco": null
}`;
JSON.stringify() — convertendo para string
const usuario = {
nome: "Carlos",
idade: 32,
email: "carlos@email.com",
ativo: true,
};
// Básico
const jsonString = JSON.stringify(usuario);
console.log(jsonString);
// {"nome":"Carlos","idade":32,"email":"carlos@email.com","ativo":true}
// Com indentação — muito mais legível
const jsonFormatado = JSON.stringify(usuario, null, 2);
console.log(jsonFormatado);
/*
{
"nome": "Carlos",
"idade": 32,
"email": "carlos@email.com",
"ativo": true
}
*/
// Com indentação de tab
const jsonTab = JSON.stringify(usuario, null, " ");
JSON.parse() — convertendo para objeto
const jsonString = '{"nome":"Ana","idade":28,"ativo":true}';
const objeto = JSON.parse(jsonString);
console.log(objeto.nome); // "Ana"
console.log(objeto.idade); // 28 — número, não string!
console.log(objeto.ativo); // true — boolean, não string!
console.log(typeof objeto.idade); // "number"
O que stringify inclui e o que ignora
Esse é um ponto crítico que causa bugs sutis:
const dados = {
nome: "Pedro",
idade: 25,
saudar: function() { return "olá"; }, // ❌ ignorado
indefinido: undefined, // ❌ ignorado
simbolo: Symbol("id"), // ❌ ignorado
data: new Date(), // ⚠️ convertido para string ISO
regex: /padrão/g, // ⚠️ vira objeto vazio {}
infinito: Infinity, // ⚠️ vira null
naoNumero: NaN, // ⚠️ vira null
nulo: null, // ✅ mantido como null
};
console.log(JSON.stringify(dados, null, 2));
/*
{
"nome": "Pedro",
"idade": 25,
"data": "2025-01-15T10:30:00.000Z",
"regex": {},
"infinito": null,
"naoNumero": null,
"nulo": null
}
*/
// saudar, indefinido e simbolo desapareceram silenciosamente!
O replacer — filtrando e transformando
O segundo argumento do JSON.stringify pode ser uma função ou array para controlar o que é incluído:
const usuario = {
id: 1,
nome: "Lucia",
email: "lucia@email.com",
senha: "hashDaSenha123", // não deve ser serializado!
saldo: 5000,
createdAt: new Date(),
};
// ── Array replacer — inclui apenas os campos listados
const jsonPublico = JSON.stringify(usuario, ["id", "nome", "email"], 2);
console.log(jsonPublico);
/*
{
"id": 1,
"nome": "Lucia",
"email": "lucia@email.com"
}
*/
// ── Function replacer — controle total
const jsonCustom = JSON.stringify(usuario, (chave, valor) => {
// Ignora campos sensíveis
if (chave === "senha") return undefined;
// Formata datas de forma diferente
if (valor instanceof Date) {
return valor.toLocaleDateString("pt-BR");
}
// Mascara o saldo
if (chave === "saldo") {
return "R$ ***";
}
return valor; // retorna normalmente
}, 2);
console.log(jsonCustom);
/*
{
"id": 1,
"nome": "Lucia",
"email": "lucia@email.com",
"saldo": "R$ ***",
"createdAt": "15/01/2025"
}
*/
O reviver — transformando ao fazer parse
O segundo argumento do JSON.parse é uma função reviver que transforma valores ao deserializar:
const jsonString = `{
"nome": "Rafael",
"nascimento": "1995-03-20T00:00:00.000Z",
"ultimoAcesso": "2025-01-15T14:30:00.000Z",
"saldo": "1500.50"
}`;
const objeto = JSON.parse(jsonString, (chave, valor) => {
// Converte strings de data de volta para objetos Date
if (chave === "nascimento" || chave === "ultimoAcesso") {
return new Date(valor);
}
// Converte saldo de volta para número
if (chave === "saldo") {
return parseFloat(valor);
}
return valor;
});
console.log(objeto.nascimento instanceof Date); // true
console.log(objeto.nascimento.getFullYear()); // 1995
console.log(objeto.saldo + 100); // 1600.5 — número real
O problema das datas com JSON
Datas são o caso mais comum de perda de tipo com JSON:
const evento = {
titulo: "Conferência JavaScript",
data: new Date("2025-06-15"),
duracao: 8 * 60 * 60 * 1000, // 8 horas em ms
};
// Stringify converte Date para string ISO
const json = JSON.stringify(evento);
console.log(json);
// {"titulo":"Conferência JavaScript","data":"2025-06-15T00:00:00.000Z","duracao":28800000}
// Parse não reconstrói o Date automaticamente
const recuperado = JSON.parse(json);
console.log(typeof recuperado.data); // "string" — não é Date!
console.log(recuperado.data instanceof Date); // false
// ✅ Soluções:
// 1. Reviver manual
const correto = JSON.parse(json, (chave, valor) => {
if (chave === "data") return new Date(valor);
return valor;
});
console.log(correto.data instanceof Date); // true
// 2. Reconstituir após o parse
const obj = JSON.parse(json);
obj.data = new Date(obj.data);
// 3. Guardar timestamp (número) em vez de Date
const eventoSeguro = {
titulo: "Conferência JavaScript",
dataTimestamp: new Date("2025-06-15").getTime(), // número — sobrevive ao JSON
};
Cópia profunda com JSON
Um truque popular para fazer deep clone de objetos simples:
const original = {
nome: "Beatriz",
endereco: {
cidade: "Porto Alegre",
estado: "RS",
},
hobbies: ["leitura", "programação"],
};
// ✅ Deep clone simples com JSON
const copia = JSON.parse(JSON.stringify(original));
// Modifica a cópia
copia.endereco.cidade = "Florianópolis";
copia.hobbies.push("ciclismo");
// Original intacto
console.log(original.endereco.cidade); // "Porto Alegre"
console.log(original.hobbies.length); // 2
Mas esse truque tem limitações — perde funções, datas, undefined, NaN e referências circulares. Para casos complexos, use structuredClone():
// ✅ structuredClone — deep clone nativo e robusto (ES2022)
const copia = structuredClone(original);
// Preserva Dates, RegExp, Map, Set, ArrayBuffer...
// Lança erro em referências circulares
Referências circulares — o erro mais temido
const pessoa = { nome: "João" };
pessoa.amigo = pessoa; // referência circular!
// ❌ Isso lança um erro
JSON.stringify(pessoa);
// TypeError: Converting circular structure to JSON
// ✅ Solução com replacer
function stringifySeguro(obj) {
const vistos = new WeakSet();
return JSON.stringify(obj, (chave, valor) => {
if (typeof valor === "object" && valor !== null) {
if (vistos.has(valor)) {
return "[Referência Circular]";
}
vistos.add(valor);
}
return valor;
});
}
console.log(stringifySeguro(pessoa));
// {"nome":"João","amigo":"[Referência Circular]"}
JSON5 e outras variantes
O JSON padrão é rígido. Existe o JSON5 — uma extensão não oficial que permite:
// JSON5 — mais permissivo (não é padrão!)
{
// Comentários são permitidos
nome: "Ana", // chaves sem aspas
'idade': 28, // aspas simples
hobbies: [
"JS",
"Node", // trailing comma permitida
],
salario: 5_000, // underscore em números
infinito: Infinity, // valores especiais
}
JSON5 não é suportado nativamente — precisa de uma biblioteca. Para arquivos de configuração (como tsconfig.json e .eslintrc), algumas ferramentas aceitam comentários — na prática é JSONC (JSON with Comments), não JSON5.
Trabalhando com JSON em APIs reais
Padrões comuns que você vai encontrar:
// ── Resposta típica de uma API REST ────────────────
const respostaAPI = {
"success": true,
"data": {
"usuarios": [
{ "id": 1, "nome": "Ana" },
{ "id": 2, "nome": "Bruno" },
],
"total": 2,
"pagina": 1,
"porPagina": 10
},
"meta": {
"versao": "1.0",
"timestamp": "2025-01-15T10:30:00Z"
}
};
// Extraindo dados com desestruturação
const { data: { usuarios, total, pagina } } = respostaAPI;
console.log(`${total} usuários — página ${pagina}`);
usuarios.forEach(u => console.log(u.nome));
// ── Paginação ─────────────────────────────────────
async function buscarTodosUsuarios() {
const todos = [];
let pagina = 1;
let temMais = true;
while (temMais) {
const response = await fetch(
`https://api.exemplo.com/usuarios?pagina=${pagina}&por_pagina=10`
);
const { data } = await response.json();
todos.push(...data.usuarios);
temMais = data.usuarios.length === 10; // se veio menos, acabou
pagina++;
}
return todos;
}
Validação de JSON recebido
Nunca confie cegamente em dados de uma API externa:
function validarUsuario(dados) {
const erros = [];
if (!dados || typeof dados !== "object") {
throw new Error("Dados inválidos — esperado um objeto.");
}
if (!dados.id || typeof dados.id !== "number") {
erros.push("id deve ser um número.");
}
if (!dados.nome || typeof dados.nome !== "string" || dados.nome.trim().length < 2) {
erros.push("nome deve ser uma string com pelo menos 2 caracteres.");
}
if (!dados.email || !dados.email.includes("@")) {
erros.push("email inválido.");
}
if (erros.length > 0) {
throw new Error(`Dados inválidos:
- ${erros.join("
- ")}`);
}
return true;
}
async function buscarEValidar(id) {
const response = await fetch(`https://api.exemplo.com/usuarios/${id}`);
const dados = await response.json();
try {
validarUsuario(dados);
return dados;
} catch (erro) {
console.error(`Resposta inválida da API: ${erro.message}`);
throw erro;
}
}
Utilitário completo para JSON
const Json = {
// Parse seguro com valor padrão
parseSafe(texto, padrao = null) {
try {
return JSON.parse(texto);
} catch {
return padrao;
}
},
// Stringify com tratamento de circulares
stringifySafe(obj, espacos = 0) {
const vistos = new WeakSet();
return JSON.stringify(obj, (chave, valor) => {
if (typeof valor === "object" && valor !== null) {
if (vistos.has(valor)) return "[Circular]";
vistos.add(valor);
}
return valor;
}, espacos);
},
// Deep clone
clonar(obj) {
return this.parseSafe(JSON.stringify(obj));
},
// Verificar se uma string é JSON válido
eValido(texto) {
try {
JSON.parse(texto);
return true;
} catch {
return false;
}
},
// Mesclar objetos profundamente via JSON (para objetos simples)
mesclar(...objetos) {
return objetos.reduce((acc, obj) => ({
...acc,
...JSON.parse(JSON.stringify(obj)),
}), {});
},
};
// Uso
console.log(Json.eValido('{"nome": "Ana"}')); // true
console.log(Json.eValido("isso não é json")); // false
const clone = Json.clonar({ a: { b: { c: 42 } } });
console.log(clone.a.b.c); // 42
const seguro = Json.stringifySafe({ nome: "João", ref: null });
console.log(seguro); // {"nome":"João","ref":null}
Tarefa para você
Construa um sistema de exportação e importação de dados:
// 1. Crie um array de 5 produtos com:
// { id, nome, preco, categoria, criadoEm: new Date(), ativo }
// 2. Exporte para JSON formatado, mas:
// - Formata datas como "DD/MM/AAAA"
// - Remove produtos inativos
// - Mascara preços acima de R$1000 como "Preço sob consulta"
// 3. Importe de volta do JSON e:
// - Reconstrói as datas como objetos Date
// - Valida que cada produto tem id, nome e preco
// - Lança erro descritivo se algum campo estiver faltando
// 4. Exiba um relatório:
// - Total de produtos exportados/importados
// - Categorias únicas encontradas
// - Produto mais caro
// - Data de criação mais antiga
Conclusão
Neste artigo você aprendeu:
- As regras rígidas do JSON e como diferem do objeto JavaScript
JSON.stringify()com replacer e indentaçãoJSON.parse()com reviver para reconstituir tipos- O que stringify ignora silenciosamente — a armadilha das funções e
undefined - O problema clássico das datas com JSON
- Deep clone com JSON e a alternativa moderna
structuredClone - Como lidar com referências circulares
- Paginação e validação de respostas de APIs
- Um utilitário completo e reutilizável para JSON
No próximo artigo vamos aprender sobre tratamento de erros em requisições — como lidar com timeouts, falhas de rede, erros de API e criar uma estratégia robusta de resiliência.
📌 Próximo artigo: Aula 24 — Tratamento de erros em requisições HTTP
📚 Fontes e Referências
- MDN Web Docs — JSON: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/JSON
- MDN Web Docs — JSON.stringify: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
- MDN Web Docs — JSON.parse: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
- MDN Web Docs — structuredClone: https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
- JavaScript.info — JSON methods: https://javascript.info/json
- JSON5 Spec: https://json5.org
- Douglas Crockford — Introducing JSON: https://www.json.org/json-en.html
- JavaScript: The Good Parts — Douglas Crockford (O'Reilly)
- You Don't Know JS: Types & Grammar — Kyle Simpson: https://github.com/getify/You-Dont-Know-JS