JSON em JavaScript: JSON.parse e JSON.stringify
As regras que o JSON não perdoa, o que o stringify descarta em silêncio e como ler os dois erros mais comuns: token < inesperado e estrutura circular.
JSON é um formato de texto para transportar dados. JSON.stringify pega um
valor JavaScript e devolve a string; JSON.parse pega a string e devolve um
valor novo. As duas funções vivem no objeto global JSON e não precisam de
importação nenhuma, nem no navegador nem no Node.
Resposta curta: stringify serializa, parse desserializa, e a volta nunca é
idêntica à ida. Data vira texto, undefined some, função some, e o objeto que
volta é outro objeto — com os mesmos valores, mas sem nenhuma ligação com o
original.
Os nomes técnicos são serialização e desserialização. Em palavras
simples, stringify empacota certos dados como texto e parse monta um novo
valor a partir desse texto. Não é pausa do objeto nem cópia perfeita de tudo.
A mudança para caixas: o que cabe e o que fica para trás
Numa mudança, você coloca em caixas o que pode ser transportado, escreve
etiquetas e remonta o ambiente no destino. A casa original não viaja inteira:
alguns objetos ficam, e a montagem nova não é o mesmo endereço. JSON faz esse
transporte com os tipos que o formato conhece; função e undefined ficam para
trás, enquanto data perde o tipo e vira texto.
Antes do primeiro exemplo, liste tipo, valor e identidade esperados depois da
ida e volta. Execute e confira com typeof, igualdade de valor e comparação por
referência. Assim você verifica três propriedades diferentes em vez de resumir
tudo a “o objeto voltou”.
const pedido = {
numero: 8412,
cliente: 'Ana Souza',
itens: [{ sku: 'TEC-01', preco: 289.9, quantidade: 1 }],
freteGratis: true,
};
const texto = JSON.stringify(pedido);
console.log(texto);
console.log(typeof texto, texto.length);
const devolta = JSON.parse(texto);
console.log(devolta.itens[0].sku, devolta.freteGratis);
console.log(devolta === pedido);A última linha é o ponto que mais gera confusão: devolta tem o mesmo conteúdo,
mas é um objeto novo. Nada que você mudar nele afeta pedido. É por isso que a
dupla parse(stringify(x)) virou um clone de pobre — e por que ela é uma má
ideia: tudo que o stringify descarta (Date, undefined, função, NaN)
some no caminho, e uma referência circular derruba o clone com exceção.
Para leitura humana, o terceiro argumento define a indentação:
const pedido = {
numero: 8412,
cliente: 'Ana Souza',
endereco: { cidade: 'São Paulo', uf: 'SP' },
};
console.log(JSON.stringify(pedido, null, 2));O que o JSON não aceita
A sintaxe parece a de objeto JavaScript, mas é bem mais estreita:
| em JavaScript vale | em JSON |
|---|---|
{ numero: 8412 } |
proibido — chave sempre entre aspas duplas |
{ 'cliente': 'Ana' } |
proibido — aspas simples não existem |
{ "a": 1, } |
proibido — vírgula final derruba o parse |
// comentário |
proibido — não existe comentário em JSON |
{ "total": 289.9 } |
válido |
Só seis tipos entram: string, número, booleano, null, objeto e array. Não
existe data, não existe undefined, não existe NaN, não existe função.
O que o stringify descarta em silêncio
Esta é a parte que pega gente experiente, porque não há erro nenhum — os campos simplesmente somem:
const pedido = {
numero: 8412,
cupom: undefined,
calcularTotal() {
return 289.9;
},
[Symbol('interno')]: 'some',
observacao: null,
};
console.log(JSON.stringify(pedido));
const historico = [8412, undefined, () => 1, Symbol('x'), 8413];
console.log(JSON.stringify(historico));
console.log(JSON.stringify({ desconto: NaN, frete: Infinity }));Três comportamentos diferentes numa saída só:
- Em objeto,
undefined, função eSymbolsão omitidos — a chave nem aparece.nullfica, porquenullé um valor válido em JSON. - Em array, omitir mudaria os índices, então eles viram
null. A posição é preservada, o valor não. NaNeInfinitytambém viramnull. Se o seu cálculo produziuNaN, o JSON não vai te avisar: ele mandanullpara o servidor.
Data vira texto ISO — e não volta sozinha
const pedido = {
numero: 8412,
criadoEm: new Date('2026-03-15T14:30:00-03:00'),
};
const texto = JSON.stringify(pedido);
console.log(texto);
const devolta = JSON.parse(texto);
console.log(typeof devolta.criadoEm);
console.log(devolta.criadoEm.getFullYear);Duas coisas aconteceram. O horário virou UTC — 14:30 em Brasília é 17:30Z, e
esse deslocamento é assunto da lição sobre
datas em JavaScript. E o que voltou foi uma
string: getFullYear nem existe nela. Chamar um método de Date num valor
desses produz o velho is not a function.
O segundo argumento do parse conserta isso na entrada:
const texto = '{"numero":8412,"criadoEm":"2026-03-15T17:30:00.000Z"}';
const pedido = JSON.parse(texto, (chave, valor) => {
if (chave === 'criadoEm' && typeof valor === 'string') return new Date(valor);
return valor;
});
console.log(pedido.criadoEm instanceof Date);
console.log(pedido.criadoEm.toLocaleDateString('pt-BR'));Essa função é o reviver: ela é chamada para cada par chave/valor e o que ela devolve é o que entra no objeto final. Repare que eu testei o nome da chave em vez de sair convertendo toda string que pareça uma data — regex genérica de ISO tem o hábito de transformar código de rastreio em data.
replacer: escolher e transformar na saída
O segundo argumento do stringify faz o mesmo trabalho na direção contrária.
Ele aceita uma lista de chaves permitidas ou uma função:
const cliente = {
nome: 'Ana Souza',
email: 'ana@exemplo.com.br',
senha: 'nao-vai-para-o-log',
cpf: '123.456.789-00',
total: 289.94999,
};
console.log(JSON.stringify(cliente, ['nome', 'email']));
console.log(
JSON.stringify(cliente, (chave, valor) => {
if (chave === 'senha' || chave === 'cpf') return undefined;
if (chave === 'total') return Number(valor.toFixed(2));
return valor;
}),
);Devolver undefined na função apaga a chave — é a mesma regra do descarte que
vimos acima, agora usada de propósito. É o jeito mais direto de garantir que
senha e CPF não vazem para um log.
toJSON: o objeto decide como se serializa
Se um valor tem um método toJSON, o stringify chama esse método e serializa
o que ele devolver. Date faz exatamente isso — e a sua classe pode fazer
também:
class Pedido {
#chaveDoGateway = 'sk_live_naovaipro_json';
constructor(numero, cliente, total) {
this.numero = numero;
this.cliente = cliente;
this.total = total;
}
toJSON() {
return {
numero: this.numero,
cliente: this.cliente,
total: Number(this.total.toFixed(2)),
};
}
}
const pedido = new Pedido(8412, 'Ana Souza', 289.94999);
console.log(JSON.stringify(pedido));
console.log(JSON.stringify({ ultimo: pedido }));A segunda linha mostra que a regra vale em qualquer profundidade: o objeto
aninhado também passou pelo próprio toJSON. Campo privado, aliás, nunca sairia
mesmo — mas com toJSON você controla o formato inteiro em vez de torcer.
Erros comuns
Aspas simples e vírgula final
const texto = "{'numero': 8412, 'cliente': 'Ana Souza'}";
console.log(JSON.parse(texto));const texto = `{
"numero": 8412,
"cliente": "Ana Souza",
}`;
console.log(JSON.parse(texto));Repare no cabeçalho: o Node não mostra o seu arquivo primeiro, e sim
<anonymous_script> — o texto que estava sendo lido, com o cursor no caractere
que travou. O } da última linha só ficou errado porque a vírgula anterior
prometeu mais uma chave. E a mensagem ainda dá linha, coluna e posição: com
elas, texto.slice(40, 60) mostra a vizinhança do problema na hora.
Unexpected token '<' — você não recebeu JSON
O erro mais comum de todos não é de JSON malformado. É de resposta que nunca foi JSON:
const resposta = `<!DOCTYPE html>
<html lang="pt-BR">
<head><title>502 Bad Gateway</title></head>
</html>`;
const pedido = JSON.parse(resposta);
console.log(pedido.numero);O cursor aponta para o primeiro caractere do corpo, e o Node ainda imprime a
linha inteira: <!DOCTYPE html>. É o começo de uma página HTML — erro de
gateway, tela de login, página 404 do servidor. A URL está errada, a sessão caiu
ou o serviço está fora. Não adianta mexer no parse: o problema é a requisição.
O diagnóstico leva dez segundos:
const resposta = `<!DOCTYPE html>
<html lang="pt-BR">
<head><title>502 Bad Gateway</title></head>
</html>`;
try {
JSON.parse(resposta);
} catch (erro) {
console.log('não era JSON. Os 60 primeiros caracteres do corpo:');
console.log(resposta.slice(0, 60));
console.log('mensagem:', erro.message);
}Estrutura circular
JSON é uma árvore. Se o seu objeto tem um ciclo, não existe texto que o represente:
const cliente = { nome: 'Ana Souza' };
const pedido = { numero: 8412, cliente };
cliente.ultimoPedido = pedido;
console.log(JSON.stringify(pedido));Essa mensagem é das melhores do Node: ela desenha o caminho do ciclo, campo por
campo. Aqui, pedido.cliente.ultimoPedido volta para pedido.
A saída definitiva é quebrar o ciclo no modelo de dados — guardar
ultimoPedidoId em vez do objeto inteiro. Quando você só precisa logar, um
replacer com WeakSet resolve:
const cliente = { nome: 'Ana Souza' };
const pedido = { numero: 8412, cliente };
cliente.ultimoPedido = pedido;
const vistos = new WeakSet();
const texto = JSON.stringify(pedido, (chave, valor) => {
if (typeof valor === 'object' && valor !== null) {
if (vistos.has(valor)) return '[circular]';
vistos.add(valor);
}
return valor;
});
console.log(texto);Antes de mandar para produção
Quatro hábitos que evitam quase todo incidente com JSON:
- Todo
JSON.parsede origem externa vai dentro detry. Você não controla o que o servidor devolve, e um parse solto derruba a requisição inteira. - Nunca confie no formato do que voltou. Depois do parse você tem um objeto
qualquer, não o seu tipo: leia os campos opcionais com
?.e??. - Não use
parse(stringify(x))para clonar. UsestructuredClone, que preservaDatee não morre em ciclo. - Serialize dinheiro com cuidado.
289.94999só virou289.95porque eu arredondei noreplacer; sem isso, o valor cru viaja com todas as casas.
A trilha de JavaScript segue com datas, o tipo que o JSON transforma em texto e devolve pela metade. Todo o material de JavaScript está reunido no guia da linguagem.
Prefere aprender em vídeo?
Tem uma aula sobre este assunto no nosso canal.
Perguntas frequentes
JSON e objeto JavaScript são a mesma coisa?
Por que a data volta como string depois do JSON.parse?
Dá para usar JSON.parse(JSON.stringify(obj)) para clonar?
Como descobrir por que um JSON.parse falhou?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — JSON — developer.mozilla.org
- MDN — JSON.stringify() — developer.mozilla.org



