Geonode logo
Geonode Team

Geonode Team

Atualizado: 7 de outubro de 2026

Publicado: 2 de setembro de 2026

JSON.parse() em JavaScript: Um guia completo

`JSON.parse()` transforma uma cadeia de caracteres num valor JavaScript. Essa é toda a API, e demora cerca de dez segundos a aprender. O que é interessante é tudo o que a rodeia: a função «reviver», a perda de precisão numérica que passa despercebida na produção, o caso especial «`__proto__`» e a recente adição à norma que finalmente permite ver o texto original. Este guia aborda tudo isto, incluindo os modos de falha, em vez de se limitar apenas ao cenário ideal.

Uma nota sobre o motivo pelo qual uma empresa de proxies está a escrever sobre JSON.parse. Somos a Geonode e vendemos proxies, e o SyntaxError mais comum que os nossos clientes relatam não tem absolutamente nada a ver com JSON — trata-se de Unexpected token '<', o que significa que a resposta foi em HTML em vez de JSON, ou seja, a API devolveu uma página de bloqueio, um redirecionamento para o login ou uma página de erro. Por isso, eis a advertência sincera: se o JSON.parse estiver a apresentar erros nos dados que obteve, registe o corpo da resposta em bruto antes de alterar qualquer outra coisa. Nove em cada dez vezes, o analisador está a funcionar na perfeição e a indicar corretamente que lhe foi enviada uma página web. A compra de proxies resolve isso apenas no caso específico em que foi bloqueado; não resolve nada no caso de um URL errado, um token expirado ou um limite de taxa que deva respeitar. Imprima primeiro a resposta.

Com isso esclarecido, passemos à API propriamente dita.

Os conceitos básicos e os erros com que se vai deparar

const data = JSON.parse('{"name": "Ada", "born": 1815}');
// { name: "Ada", born: 1815 }

Dois parâmetros: o texto e uma função «reviver» opcional. É tudo.

Lança uma exceção «SyntaxError

» quando a entrada viola a gramática JSON, e a gramática JSON é mais rigorosa do que a sintaxe dos literais de objeto do JavaScript, de formas que podem apanhar as pessoas desprevenidas. Quatro casos são responsáveis por quase todas as falhas.

Aspas simples. MDN é explícito: «As cadeias de caracteres JSON devem ser delimitadas por aspas duplas (não simples).» JavaScript válido, JSON inválido.

JSON.parse("{'name': 'Ada'}");  // SyntaxError
JSON.parse('{"name": "Ada"}');  // fine

Vírgulas finais. Válidas no JavaScript moderno, inválidas no JSON:

JSON.parse("[1, 2, 3, 4, ]");  // SyntaxError

Chaves sem aspas. {name: "Ada"}

é um literal de objeto válido, mas não é JSON. As chaves têm de ser cadeias de caracteres entre aspas.

A resposta não era JSON. A descrita acima. Unexpected token '<'

significa que o corpo começava com <

, o que significa HTML. Unexpected end of JSON input

significa normalmente um corpo vazio — um 204, uma resposta truncada ou um fetch que se esqueceu de aguardar.

Envolva-o, sempre, e registe o que recebeu efetivamente:

function parseOrThrow(text, url) {
  try {
    return JSON.parse(text);
  } catch (err) {
    throw new Error(
      `Failed to parse JSON from ${url}: ${err.message}. ` +
      `First 200 chars: ${text.slice(0, 200)}`
    );
  }
}

O text.slice(0, 200)

é a parte que importa. Um simples SyntaxError

indica que a análise falhou; os primeiros 200 caracteres explicam o motivo, e normalmente a resposta é visível imediatamente.

A função «Reviver» e para que serve

O segundo argumento transforma os valores à medida que estes são analisados:

const data = JSON.parse(text, (key, value) => {
  if (key === "created") return new Date(value);
  return value;
});

Três comportamentos que vale a pena conhecer com precisão.

Funciona em profundidade. As propriedades aninhadas são visitadas antes das suas propriedades-mãe, e a chamada final utiliza uma cadeia de caracteres vazia como chave para o valor raiz. Assim, quando o seu reviver encontra um objeto, os seus filhos já foram processados.

**O retorno de ``undefined`

elimina a propriedade.** MDN: «Se a função ``reviver

devolver ``undefined

(ou não devolver qualquer valor), a propriedade é eliminada do objeto.» É fácil provocar isto acidentalmente — um reviver com um ramo condicional que não é executado até ao fim devolveundefined`

e remove chaves silenciosamente. Deve devolver sempre explicitamente value

como valor por predefinição.

A raiz pode ser substituída na totalidade. «Se devolver outro valor a partir de reviver

, esse valor substituirá completamente o valor originalmente analisado. Isto aplica-se mesmo ao valor raiz."

A utilização clássica é a recuperação de datas, uma vez que o JSON não possui um tipo de dados para datas:

const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;

const data = JSON.parse(text, (key, value) =>
  typeof value === "string" && ISO_DATE.test(value)
    ? new Date(value)
    : value
);

Tenha cuidado com a correspondência de padrões. Um reviver que converta qualquer coisa com a forma de uma data irá converter cadeias de caracteres que pretendia manter como tal — números de versão, identificadores, conteúdo do utilizador que por acaso se pareça com um carimbo temporal. É preferível fazer a correspondência com base no nome da chave, sempre que conhecer o esquema.

Tenha também em conta o custo: o reviver é chamado uma vez por valor no documento. Em cargas úteis de grande dimensão, isto é uma consideração real em termos de desempenho, e muitas vezes é mais económico analisar o conteúdo de forma simples e transformar posteriormente apenas os campos que lhe interessam.

Acesso ao texto-fonte: context.source e JSON.rawJSON

Esta é a novidade mais significativa dos últimos tempos e muitos programadores ainda não a conheceram.

A proposta de acesso ao texto-fonte do JSON.parse atingiu a fase 4 do processo do TC39, o que significa que foi aprovada para a norma. Resolve um problema que a proposta identifica diretamente: «A transformação entre valores ECMAScript e texto JSON implica perdas.»

O reviver recebe agora um terceiro argumento para valores primitivos. O MDN descreve context.source

como «A cadeia JSON original que representa este valor» — e a proposta é mais precisa, referindo-se a ela como o texto de origem «incluindo pontuação, mas excluindo espaços em branco insignificantes no início e no fim», juntamente com index

, input

e keys

.

A importância disto torna-se óbvia com um exemplo:

const text = '{"id": 9007199254740993}';

JSON.parse(text).id;
// 9007199254740992  — wrong, silently

JSON.parse(text, (key, value, context) =>
  key === "id" ? BigInt(context.source) : value
).id;
// 9007199254740993n  — correct

Sem acesso à fonte, o número já foi convertido para um tipo «double» do JavaScript quando o seu reviver o deteta. A precisão desaparece antes de poder intervir. context.source

fornece-lhe os dígitos originais.

A proposta também adiciona JSON.rawJSON()

, que permite fornecer texto JSON bruto que JSON.stringify

emite sem alterações — completando o ciclo para que um BigInt lido do JSON possa ser reescrito sem corrupção.

JSON.stringify({ id: JSON.rawJSON("9007199254740993") });
// '{"id":9007199254740993}'

Verifique a compatibilidade com os seus ambientes de destino antes de confiar nisto, mas esta é agora a resposta correta para o problema dos números grandes, em vez de uma solução alternativa.

A precisão numérica é o erro que acabará por lançar

Merece uma secção própria porque falha silenciosamente e os sintomas surgem longe da causa.

Os números JSON transformam-se em números JavaScript, que são valores duplos IEEE 754. Os números inteiros acima de Number.MAX_SAFE_INTEGER — 9 007 199 254 740 991 — não podem ser todos representados com exatidão. O MDN explica-o claramente: os números «podem perder precisão no processo».

O perigo reside no facto de não ser lançada qualquer exceção. Obtém-se um número. Simplesmente, não é o número que foi enviado.

JSON.parse('{"id": 12345678901234567890}').id;
// 12345678901234567000

Onde isto se torna problemático na prática:

  • Identificadores de bases de dados. As chaves primárias de inteiros de 64 bits excedem o intervalo seguro. Dois registos distintos podem ser convertidos no mesmo número JavaScript.
  • ID-s do tipo «Snowflake». Utilizados por várias plataformas de grande dimensão, ultrapassam habitualmente o limite.
  • Valores financeiros em unidades menores. Grandes somas em cêntimos ou satoshis.
  • Carimbos de data/hora em nanossegundos. Qualquer valor de época em nanossegundos desde 1970 já se encontra fora do intervalo seguro.

Três medidas de mitigação, por ordem de preferência:

Solicitar cadeias de caracteres. Se controlar a API, serialize identificadores grandes como cadeias de caracteres. Esta é a solução mais simples, funciona em qualquer lugar e não tem qualquer custo. A MDN recomenda exatamente isto: «Uma forma de transferir números grandes sem perda de precisão é serializá-los como cadeias de caracteres e reconvertê-los em BigInts.»

Utilize «context.source» com um reviver. Tal como acima, quando não controla o produtor e o seu ambiente suporta essa funcionalidade.

Utilize uma biblioteca JSON com suporte a BigInt. Para ambientes mais antigos, vários analisadores lidam com isto. Implica uma dependência e alguma perda de desempenho.

O que não funciona: verificar se o número «parece correto» após a análise. Nessa altura, a informação já se perdeu e um valor errado é indistinguível de um valor correto.

«__proto__» e «Prototype Pollution»

O MDN identifica o único caso em que o JSON e o JavaScript divergem em termos de significado: «A única situação em que um trecho de texto JSON representa um valor diferente da mesma expressão JavaScript é quando se lida com a chave «\"__proto__\"».»

Num objeto literal de JavaScript, __proto__ define o protótipo. Em JSON.parse, cria uma propriedade própria comum: `

const fromLiteral = { __proto__: { admin: true } };
fromLiteral.admin;             // true — prototype was set

const fromJson = JSON.parse('{"__proto__": {"admin": true}}');
fromJson.admin;                // undefined — plain own property
Object.hasOwn(fromJson, "__proto__");  // true

Portanto, JSON.parse em si é seguro neste contexto — trata-se de um comportamento deliberado e correto.

O perigo reside no que acontece a seguir. As vulnerabilidades de poluição de protótipos ocorrem quase sempre quando os dados analisados são incorporados noutro objeto por código que não filtra chaves perigosas:

// Unsafe: a naive deep merge can walk into Object.prototype
function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === "object") {
      merge(target[key] ?? (target[key] = {}), source[key]);
    } else {
      target[key] = source[key];
    }
  }
}

Alimente-o com uma carga útil que contenha __proto__ e poderá modificar Object.prototype para todo o programa. Defesas:

Filtre explicitamente as chaves perigosas — __proto__, constructor, prototype — em qualquer fusão ou atribuição que envolva dados não confiáveis.

Utilize Object.create(null) para objetos que contenham chaves não confiáveis, para que não haja nenhum protótipo para contaminar.

Utilize Map quando estiver realmente a criar um armazenamento de chave-valor, em vez de um objeto estruturado.

Valide em relação a um esquema. Esta é a resposta geral e a que também deteta outros problemas. A análise e a validação são etapas separadas e ambas são necessárias.

JSON.parse versus eval versus Response.json()

Nunca utilize eval. Este executa código arbitrário, é mais lento para este fim e aceita dados que não são JSON. Não há nenhum caso em que eval seja a ferramenta adequada para analisar JSON.

Response.json() é o que deve utilizar ao trabalhar com fetch. Este lê o corpo da mensagem e analisa-o num único passo:

const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();

A verificação res.ok é a parte que as pessoas ignoram, e ignorá-la é a causa direta do erro Unexpected token '<' mencionado na introdução. fetch não rejeita códigos de erro HTTP — um 403 ou um 500 são resolvidos normalmente e, em seguida, .json() tenta analisar uma página de erro. Verifique primeiro o código de estado e, se quiser ser minucioso, consulte content-type:

const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
const type = res.headers.get("content-type") ?? "";
if (!type.includes("application/json")) {
  const body = await res.text();
  throw new Error(`Expected JSON, got ${type}: ${body.slice(0, 200)}`);
}
const data = await res.json();

Note que Response.json() não aceita um «reviver». Se precisar de um, utilize res.text() seguido de JSON.parse.

As bibliotecas também diferem neste aspeto — algumas analisam automaticamente e lançam uma exceção em códigos de estado que não sejam 2xx, o que altera o local onde o tratamento de erros deve ser efetuado. Comparámos o comportamento em axios vs fetch.

Analisar JSON de grande dimensão sem bloquear a página

JSON.parse é síncrono e bloqueante. Na thread principal, a análise de um documento de grande dimensão bloqueia a interface enquanto dura — esta é uma causa comum e facilmente diagnosticável de lentidão.

Orientação geral: abaixo de um megabyte, nem vale a pena pensar nisso. Entre um e dez, faça um teste no seu dispositivo-alvo mais lento. Acima de dez, opte por outra solução.

As opções, por ordem crescente de esforço:

Transfira a tarefa para um Web Worker. A solução real mais simples. Analise fora da thread principal e envie o resultado de volta. Note que a transferência do resultado tem o seu próprio custo de clonagem estruturada, pelo que isto ajuda mais quando o worker também realiza o processamento subsequente.

Solicite menos dados. Paginação, seleção de campos, um endpoint mais restrito. Quase sempre a resposta correta e quase sempre ignorada porque requer contactar quem detém a API.

Utilizar um analisador de streaming. Existem bibliotecas que emitem valores à medida que estes chegam, em vez de construírem a árvore completa. Vale a pena quando os documentos são genuinamente grandes ou quando só é necessária uma parte da carga útil.

Utilizar JSON delimitado por novas linhas. Para coleções grandes, um documento JSON por linha é significativamente mais fácil de processar de forma incremental, uma vez que cada linha é analisada de forma independente e um fluxo truncado ainda produz registos completos. Se controlar o formato, esta é frequentemente a melhor opção de design — e as vantagens e desvantagens em relação a outros formatos são abordadas na nossa comparação entre JSON e CSV.

Quando «JSON.parse» não é a ferramenta adequada

Quando a entrada não é JSON. O JSON5, o JSONC e os ficheiros de configuração com comentários e vírgulas finais requerem, todos, os seus próprios analisadores. O JSON.parse rejeitá-los-á corretamente e não deve tentar remover comentários com uma expressão regular — esse caminho leva a um analisador que não pretendia escrever.

Quando precisa de validação, e não apenas de análise. Uma análise bem-sucedida indica que a sintaxe era válida. Não diz nada sobre se os campos obrigatórios existem ou se têm os tipos corretos. Analise e, em seguida, valide; uma biblioteca de validação de esquemas é a ferramenta certa para o segundo passo e try/catch não o é.

Quando os dados têm de ser transferidos de ida e volta sem perdas. Números inteiros grandes, datas, undefined, funções, Map, Set, NaN, Infinity — nenhum sobrevive intacto ao JSON. Se a transferência de ida e volta sem perdas for um requisito, utilize deliberadamente context.source e JSON.rawJSON, ou utilize um formato concebido para esse fim.

Quando está a analisar a cada renderização. Analisar a mesma string repetidamente num caminho de acesso frequente é um desperdício total. Analise uma vez e guarde em cache.

Quando a string provém de uma recuperação que não verificou. O caso com que começámos, repetido porque é o mais comum de todos. Se JSON.parse estiver a lançar um erro nos dados recuperados, o bug está a montante. Verifique o código de estado, verifique o tipo de conteúdo, registe o corpo. O analisador está a dizer-lhe a verdade.

Perguntas frequentes

O que faz o JSON.parse?

Converte uma cadeia de caracteres no formato JSON num valor JavaScript — objeto, matriz, cadeia de caracteres, número, booleano ou nulo. Aceita uma função de recuperação opcional que pode transformar cada valor à medida que este é analisado. Lança uma exceção «SyntaxError» se a entrada não for JSON válido.

Por que é que o JSON.parse apresenta a mensagem «Token inesperado '<'»?

Porque a cadeia de caracteres começa por <, o que significa que recebeu HTML em vez de JSON — normalmente uma página de erro, um redirecionamento de início de sessão ou uma página de bloqueio. O analisador está correto; o problema está no pedido. Registe os primeiros 200 caracteres do corpo da resposta e a causa será normalmente óbvia.

Como analiso JSON com números grandes em JavaScript?

Utilize o argumento «context.source» do reviver para ler os dígitos originais e construir um «BigInt», uma vez que, quando o valor chega ao seu reviver, já perdeu precisão. Melhor ainda, se controlar a API, serialize identificadores grandes como cadeias de caracteres.

O que é a função «reviver» no JSON.parse?

Um segundo argumento opcional chamado para cada par chave-valor, em profundidade, terminando na raiz sob uma chave de cadeia de caracteres vazia. O que quer que devolva substitui o valor; devolver «undefined» elimina a propriedade. A sua função habitual é converter cadeias de caracteres em tipos mais ricos, como «Date».

O JSON.parse é seguro?

Contra a execução de código, sim — ao contrário de eval, nunca executa nada. Também lida com __proto__ de forma segura, criando uma propriedade própria simples em vez de definir o protótipo. O risco está no que se faz a seguir: fundir dados analisados não confiáveis noutros objetos sem filtrar __proto__ e constructor é como ocorre a poluição de protótipos.

Qual é a diferença entre o JSON.parse e o Response.json()?

O Response.json() lê o corpo de uma resposta de fetch e analisa-o numa única etapa, não aceitando um reviver. O JSON.parse funciona com uma string que já tenha. Note que o fetch não rejeita erros HTTP; por isso, verifique res.ok antes de chamar o .json(), ou acabará por analisar uma página de erro.

O JSON.parse consegue lidar com comentários ou vírgulas finais?

Não. Ambos são JSON inválido e ambos provocam um erro «SyntaxError». Se a sua entrada os contiver, trata-se de JSON5 ou JSONC, e é necessário um analisador para esse formato, em vez de uma expressão regular que os remova.

O JSON.parse bloqueia o thread principal?

Sim, é síncrono. Para documentos com menos de um megabyte, isto é irrelevante; para cargas úteis grandes, provoca um congelamento visível. Transfira a análise para um Web Worker, solicite menos dados ou utilize um analisador de streaming.

Conclusão: `

`JSON.parse`` tem uma assinatura de dois parâmetros e uma profundidade surpreendente por trás. Os aspetos que vale a pena reter são aqueles que falham discretamente, em vez de de forma evidente.

A precisão numérica é o problema mais grave: os números inteiros grandes são corrompidos silenciosamente, não é lançada nenhuma exceção e o valor errado é indistinguível do valor correto até que algo a jusante falhe. A solução passa por identificadores codificados como cadeias de caracteres na fonte ou pelo argumento context.source do reviver, agora na fase 4 e parte da norma.

O reviver merece ser mais utilizado do que é, particularmente para datas, com a ressalva de que esquecer-se de devolver um value no caminho padrão elimina silenciosamente as propriedades. E a poluição de protótipos não é, de todo, um problema do JSON.parse — a função lida com __proto__ corretamente — mas é um problema para o que quer que seja que funda o resultado, o que é suficientemente próximo para ser relevante.

Tudo o resto resume-se a um hábito: quando a análise falhar nos dados que obteve, registe o corpo bruto antes de alterar qualquer código. A mensagem de erro contém quase sempre a resposta, e normalmente é que nunca recebeu JSON de início.