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:
| Forma | Objeto JS / JSON5 | JSON.parse |
|---|---|---|
| Vírgula final | {"ok": true,} ok | lança |
| Aspas simples | {'ok': true} ok | lança |
| Comentários | // note ok | lança |
| Chaves sem aspas | {ok: true} ok | lança |
undefined / NaN / Infinity | existem na linguagem | lança |
| Quebra de linha crua numa string | template strings permitem | lanç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:
| Classe | O que o modelo costuma emitir | Resultado típico | Faça isto primeiro |
|---|---|---|---|
| Wrapper | cercas ```json, «aqui está o JSON» | O primeiro caractere não é { / [ | Retire a cerca, depois recorte um valor balanceado |
| Dialeto | Vírgula final, aspas simples, comentários, chaves sem aspas | Unexpected token | Passe para Structured Output; não parseie como JS |
| String quebrada | " sem escape, quebras de linha cruas, vírgulas fullwidth | A string acaba cedo, ou não há : depois da chave | Leia a coluna; limite o comprimento do campo |
| Truncamento | Objeto ou array sem fechar | Unexpected end of JSON input | Aumente o teto de saída; espere o stream |
| Vários valores | Dois valores JSON, ou prosa depois do primeiro | Caracteres depois do primeiro valor | Recorte só o primeiro valor completo |
| Codificação | BOM, chars de largura zero, stringify duplo | Token estranho, ou o parse devolve uma string | Remova 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.parsenum 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:
- Troque o canal. A resposta final vai por Structured Output (OpenAI
response_format.json_schema, GeminiresponseMimeTypemais Schema, Claudeoutput_config.format). Parâmetros de ferramenta vão pelosargumentsde Tool Calling, não por prosa raspada. Veja o que é Structured Output. - Extraia. Retire cercas
```json; recorte o primeiro valor balanceado; jogue fora um BOM. - Faça parse estrito. Use só
JSON.parse. Na falha, conserve o texto cru e a posição do erro. Não useeval. - 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.
- Reparo por último. Ferramentas como
jsonrepairfecham 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
SyntaxErrorcai; 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.