Por que o JSON gerado por IA falha no JSON.parse()? Causas e soluções

Em 17 de setembro de 2026: passar uma resposta de chat para JSON.parse() costuma falhar por fences, prosa ao redor, vírgulas finais, truncamento e dialeto JS — não porque o modelo não saiba escrever JSON. Mapa de causas e ordem de correção.

Conclusão primeiro: um erro de JSON.parse() em geral não significa «o modelo não sabe escrever JSON». Significa que você passou uma resposta de chat inteira para um parser que aceita um único valor JSON.JSON.parse aceita um único valor na gramática JSON (ECMA-262 / RFC 8259). Cercas Markdown, prosa ao redor, vírgula final, quebra de linha crua, truncamento e dialeto JS/Python lançam SyntaxError na hora. A ordem de conserto em 2026 é: se você puder usar Structured Output ou ler os arguments de Tool Calling, não faça parse da prosa de chat; se tiver de parsear, extraia, depois parse, depois valide com JSON Schema — não comece consertando com regex até «meio que parsear».

Escrito em 17 de setembro de 2026. Este site já tem o que é Structured Output, do prompt ao Structured Output, OpenAI vs Gemini Structured Output, JSON estruturado com a API Gemini e Tool Calling e JSON Schema. Este texto só responde por que a saída do modelo falha no JSON.parse, e em que ordem consertar.

O que o JSON.parse de fato aceita

No navegador e no Node, JSON.parse implementa texto JSON, não «um object literal de JavaScript que chega perto o bastante». Espaço em branco (espaço, tab, line feed, carriage return) pode cercar o valor. Fora isso, a entrada tem de ser exatamente um valor: object, array, string, number, true / false / null. Não-espaço depois desse valor falha — o Chrome costuma dizer Unexpected non-whitespace character after JSON.

Estas formas rodam em JS e morrem em JSON. Modelos copiam isso do treino o tempo todo:

FormaObjeto JS / JSON5JSON.parse
Vírgula final{"ok": true,} oklança
Aspas simples{'ok': true} oklança
Comentários// note oklança
Chaves sem aspas{ok: true} oklança
undefined / NaN / Infinityexistem na linguagemlança
Quebra de linha crua numa stringtemplate strings permitemlança; tem de ser \n

Depure com uma pergunta: você passou «um valor JSON», ou «um parágrafo que o modelo embrulhou para ficar legível»? O parser é dono do primeiro. O segundo você tem de extrair.

Uma tabela de classes de falha

Classifique o SyntaxError antes de brigar com o texto exato. Chrome, Safari e Node descrevem o mesmo bug com palavras diferentes. As classes são poucas:

ClasseO que o modelo costuma emitirResultado típicoFaça isto primeiro
Wrappercercas ```json, «aqui está o JSON»O primeiro caractere não é { / [Retire a cerca, depois recorte um valor balanceado
DialetoVírgula final, aspas simples, comentários, chaves sem aspasUnexpected tokenPasse para Structured Output; não parseie como JS
String quebrada" sem escape, quebras de linha cruas, vírgulas fullwidthA string acaba cedo, ou não há : depois da chaveLeia a coluna; limite o comprimento do campo
TruncamentoObjeto ou array sem fecharUnexpected end of JSON inputAumente o teto de saída; espere o stream
Vários valoresDois valores JSON, ou prosa depois do primeiroCaracteres depois do primeiro valorRecorte só o primeiro valor completo
CodificaçãoBOM, chars de largura zero, stringify duploToken estranho, ou o parse devolve uma stringRemova o BOM; cheque typeof antes de parsear de novo

Em cena de Agent, some mais uma: os arguments de Tool Calling muitas vezes já são um objeto, ou uma string JSON que o fornecedor já restringiu. Não passe a mensagem inteira do assistant por JSON.parse. Isso é outro canal — veja por que o Tool Calling depende de JSON Schema.

Cercas e prosa ao redor

Modelos de chat foram treinados para colocar código em cercas. Mesmo se você escreveu «só JSON», a resposta muitas vezes é:

```json
{"ok": true, "id": "A-1024"}
```
Here is the result. I can explain the fields if you want.

O primeiro caractere é um backtick, não {. O JSON.parse falha na coluna 0. Um «Claro, aqui está o JSON:» na frente ou um disclaimer no fim é o mesmo bug. Pior: dois valores — um exemplo, depois o resultado de verdade. Faça parse do blob inteiro e você quebra depois do primeiro }.

Extraia com uma regra só: ache o primeiro {} ou [] balanceado (pule colchetes dentro de strings) e passe só essa fatia ao JSON.parse. Retire as cercas primeiro. Não recorte guloso do primeiro { ao último } — colchetes dentro de strings, ou um segundo objeto na explicação, cortam errado.

Dialeto: vírgula final, aspas simples, comentários, chaves sem aspas

Modelos viram massas de JavaScript, Python, JSON5 e YAML. Quando pedem «dados estruturados», misturam dialetos. Tudo abaixo é ilegal para JSON.parse:

{
  ok: true,          // bare key + comment
  'name': 'Ada',     // single quotes
  "tags": ["a",],    // trailing comma
  "flag": True       // Python boolean
}

Some undefined, NaN, Infinity, None. Fazem sentido nas respectivas linguagens; JSON tem null e números finitos. Trocar JSON.parse por eval ou new Function para «aceitar» essas formas transforma o parser num sumidouro de código arbitrário. Não faça isso em produção.

JSON5 e JSONC engolem comentários e vírgula final. Serve para humano editando config. É um parser padrão ruim para saída de modelo. Quando você afrouxa a gramática, não dá mais para distinguir «vírgula a mais» de «string quebrada». Se precisar de uma camada frouxa, deixe atrás da falha de extração + parse, e ainda rode o Schema depois do reparo.

Strings e pontuação: escapes, quebras de linha, fullwidth e aspas curvas

Uma string JSON legal usa aspas duplas. O " interno e as barras invertidas precisam de escape. Caracteres de controle têm de ser \n, \t ou \uXXXX. Quando o modelo copia um comentário de usuário, aspas e quebras de linha cruas caem dentro do campo. A string acaba cedo; a próxima vírgula ou caractere CJK vira um token inesperado.

Saída CJK acrescenta um conjunto sujo frequente: vírgula fullwidth ,, dois-pontos fullwidth : e aspas curvas “” / ‘’. Parecem pontuação; os code points não são 0x2C / 0x3A / 0x22. Este «quase JSON» quebra depois do valor de name:

{
  "name": "Ada",
  "ok": true
}

Não conserte isso com mais uma frase «por favor use pontuação ASCII». Coloque maxLength em campos string longos, faça o modelo citar o texto-fonte em vez de redigitar pontuação, e use Structured Output no canal final. Na depuração, cole no validador JSON e veja em que coluna o destaque para — uma vírgula fullwidth salta aos olhos.

Truncamento e streaming: Unexpected end of JSON input

Unexpected end of JSON input quase sempre significa o texto acabou antes da gramática: falta }, falta ], ou uma string sem fechar. Em 2026 as fontes usuais são teto de tokens de saída, um corte de safety, ou você chamou JSON.parse num chunk incompleto do stream.

Uma API em streaming entrega deltas. Os primeiros chunks podem ser {"ok": tr. Faça parse disso e você falha. Faça isto em vez disso:

  • Espere o stream terminar (finish_reason / stop), depois faça parse do buffer completo;
  • Ou use um parser JSON de streaming de verdade, que avança token a token — não chame JSON.parse num valor pela metade;
  • Se o motivo da parada for length / max_tokens, isso não é bug de parse. A geração não terminou — aumente o teto, encolha o Schema, ou pagine o modelo.

Fechar chaves automaticamente depois de um truncamento é truque de rascunho. A forma pode parsear e ainda assim perder campos ou cortar uma string no meio. Depois de qualquer reparo, valide o Schema; na falha, retente. Não grave em silêncio.

Caracteres invisíveis e dupla codificação

Um BOM UTF-8 (U+FEFF) não é espaço em branco JSON. Alguns caminhos de cópia e alguns gateways prefixam isso; o JSON.parse então reporta um token inesperado na coluna 0. Espaços de largura zero e hífens suaves fazem o mesmo. Remova com replace(/^\uFEFF/, ""), depois trim, antes de extrair.

A dupla codificação é mais quieta. Um JSON.stringify produz a string "{\"ok\":true}". Faça parse dessa forma com aspas e você recebe a string {"ok":true}, não um objeto. Um segundo parse devolve o objeto. Se você parar depois de um parse e ler .ok, recebe undefined — «parseou» e não tem campos. Cheque typeof antes de parsear de novo. Não fixe «sempre parseie duas vezes»; um objeto de verdade vai lançar.

Ordem do conserto: troque o canal, depois extraia, repare por último

Essa ordem ganha de empilhar mais frases no prompt:

  1. Troque o canal. A resposta final vai por Structured Output (OpenAI response_format.json_schema, Gemini responseMimeType mais Schema, Claude output_config.format). Parâmetros de ferramenta vão pelos arguments de Tool Calling, não por prosa raspada. Veja o que é Structured Output.
  2. Extraia. Retire cercas ```json; recorte o primeiro valor balanceado; jogue fora um BOM.
  3. Faça parse estrito. Use só JSON.parse. Na falha, conserve o texto cru e a posição do erro. Não use eval.
  4. Valide o Schema. Um parse bem-sucedido só diz que a gramática é legal. Campos faltando, tipos errados e chaves a mais precisam de JSON Schema / ajv. Veja do prompt ao Structured Output.
  5. Reparo por último. Ferramentas como jsonrepair fecham chaves e tiram vírgula final. Use só depois de extração + parse falharem, e só se você aceita que o reparo pode mudar o sentido. Depois ainda rode os passos 3 e 4. Não transforme um reparador no parser padrão global.

Prompts ainda ajudam: «sem cercas, sem explicação». Eles baixam a chance de um wrapper. Eles não substituem um Schema, e não afrouxam o JSON.parse. Em 2026, tratar prosa de chat como API significa continuar pagando por cercas e truncamento.

Um pipeline pequeno de extração + parse

Um pipeline de tamanho didático: retire cercas, tire o BOM, recorte um valor balanceado, depois JSON.parse. Ele cobre wrappers comuns. Ele não conserta vírgula final nem pontuação fullwidth — deixe isso para Structured Output ou uma camada explícita de reparo.

function stripFence(text) {
  const m = String(text).match(/```(?:json|JSON)?\s*([\s\S]*?)```/);
  return m ? m[1] : String(text);
}

function sliceBalancedJson(text) {
  const src = text.replace(/^\uFEFF/, "").trim();
  const start = src.search(/[\{\[]/);
  if (start < 0) throw new SyntaxError("No JSON value found");
  const open = src[start];
  const close = open === "{" ? "}" : "]";
  let depth = 0, inStr = false, esc = false;
  for (let i = start; i < src.length; i++) {
    const ch = src[i];
    if (inStr) {
      if (esc) { esc = false; continue; }
      if (ch === "\\") { esc = true; continue; }
      if (ch === '"') inStr = false;
      continue;
    }
    if (ch === '"') { inStr = true; continue; }
    if (ch === open) depth++;
    else if (ch === close) {
      depth--;
      if (depth === 0) return src.slice(start, i + 1);
    }
  }
  throw new SyntaxError("Unterminated JSON value");
}

function parseModelJson(raw) {
  return JSON.parse(sliceBalancedJson(stripFence(raw)));
}

O recortador precisa rastrear se está dentro de uma string, senão um { no valor de um campo fecha cedo demais. Objetos e arrays aninhados usam depth. Se o recorte ainda falhar no parse, cole o texto que falhou no validador e use a tabela de classes acima. Não empilhe mais regex nesta camada.

Veja o erro no local

Não mande a saída do modelo direto para o parser de produção. No navegador, cheque três coisas: é JSON legal; se não, em que coluna; se você já tem um Schema, ele satisfaz o contrato.

  • Validador JSON — veja onde o SyntaxError cai; anexe um Schema quando você tiver um.
  • Formatador JSON — se formatar, em geral parseia; se falhar, procure vírgulas fullwidth ou cercas no original.
  • JSON Diff — depois de um parse bem-sucedido, compare o objeto do modelo com o objeto mínimo que você permite.

Nada sai do navegador. Serve para olhar lado a lado uma resposta de modelo que falhou, um Schema e um blob de arguments de Tool Calling. Estabilize nomes de campo e required, depois ligue o Host.

FAQ

Por que «parece JSON» ainda falha no JSON.parse?

O olho humano tolera cercas, vírgula final, aspas curvas e prosa ao redor. JSON.parse aceita exatamente um valor RFC 8259. Parecer JSON não é o mesmo que ser JSON legal.

Um regex que remove cercas ```json basta?

Não. Cercas são só um wrapper. Você ainda leva prosa no fim, um segundo valor JSON, vírgula final e truncamento. Depois de retirar as cercas, recorte um valor balanceado e faça parse estrito.

Qual a diferença entre JSON Mode e Structured Output?

JSON Mode em geral só restringe «parece JSON», não campos e tipos. Structured Output usa JSON Schema para bloquear tokens ilegais na decodificação. Se um programa for consumir o resultado, prefira Structured Output. Não ligue JSON Mode e depois faça JSON.parse do corpo do chat.

jsonrepair ou JSON5 deveriam ser o parser padrão?

Não. Eles aceitam entrada que deveria falhar, e podem mudar o sentido. Use só como camada de reparo depois de extração + JSON.parse falharem, e ainda valide o Schema.

Quando posso chamar JSON.parse numa resposta em stream?

Depois que o stream acabar e o buffer for um valor completo. Parsear um chunk pela metade produz Unexpected end of JSON input toda vez. Para consumir tokens conforme chegam, use um parser de streaming, não JSON.parse.

O parse deu certo, mas os campos estão errados. Isso é este artigo?

Essa é a camada seguinte. JSON.parse só garante a gramática. Campos faltando, tipos errados e chaves a mais são problema de Schema — veja os textos de Structured Output e validação de Tool Calling neste site.

Resumo

Uma falha de JSON.parse é um problema de canal. Outra frase «por favor, emita JSON» não resolve. Modelos de chat envolvem cercas, misturam dialetos e param no teto de tokens. O parser aceita um único valor JSON limpo. Em 2026, ligue o modelo por Structured Output ou arguments de Tool Calling; depois extração + parse estrito + Schema; reparo por último.

Prompts podem reduzir wrappers. Não afrouxam a gramática. Cole o texto que falhou num validador local, veja em que coluna para, e então decida: retirar uma cerca, trocar o canal, ou aumentar o teto de saída. Modelos mudam. O que o JSON.parse aceita, e qual é o contrato dos seus campos, não deveria.