Unexpected End of JSON Input: o que significa e como resolver
O parser chegou ao fim do texto antes de encontrar um valor completo e, na maioria dos casos, isso acontece porque não havia texto nenhum.
Você chama JSON.parse() ou response.json() e recebe SyntaxError: Unexpected end of JSON input. Ao contrário de um erro de token inesperado, esse erro não aponta para um caractere errado. Ele significa que o parser chegou ao último caractere enquanto ainda esperava um valor. Ou o texto estava vazio, ou parou no meio de um documento. Todo o trabalho está em distinguir esses dois casos, e para isso basta uma linha de log.
O mesmo erro em ambientes diferentes
| Onde aparece | Mensagem |
|---|---|
Chrome, Edge e Node.js, JSON.parse | Unexpected end of JSON input |
Chrome e Edge, response.json() | A mesma, às vezes precedida de Failed to execute 'json' on 'Response': |
Chrome, uma resposta sem corpo nenhum, como uma resposta opaca de no-cors | Unexpected end of input |
| Firefox | JSON.parse: unexpected end of data at line 1 column 1 of the JSON data |
| Safari | JSON Parse error: Unexpected EOF |
Python, json.loads | Expecting value: line 1 column 1 (char 0) |
Quando a mensagem menciona a linha 1, a coluna 1 ou o caractere 0, o parser não encontrou nada para ler. Texto vazio não é JSON válido: os documentos válidos mais curtos são valores como 0, null, "" ou {}.
A causa mais comum: corpo vazio ou string vazia
Antes de procurar um bug nos dados, confira se havia algum dado. Estas são as origens mais comuns de uma string vazia:
- 204 No Content. Muitas APIs respondem a um
DELETEou a umPUTbem-sucedido com status 204 e sem corpo.res.okétruepara 204, então conferir esse valor não protege a chamadares.json()que vem logo depois. - Requisições HEAD. Uma resposta a
HEADnunca tem corpo, não importa o que digam os cabeçalhos. - fetch com
mode: 'no-cors'. Isso não contorna o CORS. O resultado é uma resposta opaca, com status 0 e um corpo que o seu código não tem permissão para ler, então o parse sempre falha. Habilite o CORS no servidor ou chame a API pelo seu próprio backend. - Um 200 ou um 500 sem nada dentro. Um handler que nunca escreve a saída, ou um erro de servidor que encerra a requisição antes de enviar qualquer coisa, produz um corpo vazio com um código de status normal.
- Valores vazios em storage e em arquivos.
localStorage.setItem('cart', ''), umsettings.jsonvazio ou uma variável de ambiente definida, mas em branco, entregam uma string vazia aoJSON.parse. - Uma quebra de linha no final de JSON delimitado por linhas.
text.split('\n').map(JSON.parse)falha na string vazia que sobra depois da última quebra de linha. Filtre as linhas em branco antes.
Dois casos parecidos se comportam de forma diferente. Uma chave ausente no localStorage devolve null, e JSON.parse(null) devolve null sem lançar erro. Uma variável sem valor é undefined, que falha com "undefined" is not valid JSON; esse caso é explicado no guia Unexpected token in JSON.
Para descobrir qual é o seu caso, leia o corpo como texto antes do parse:
const res = await fetch('/api/cart', { method: 'DELETE' });
const text = await res.text();
console.log(res.status, res.type, res.headers.get('content-type'), text.length);
// Typical results:
// 204 basic null 0 -> nothing to parse
// 0 opaque null 0 -> a no-cors request
// 200 basic application/json 8192 -> there is text: check whether it is complete
No navegador, o painel Network (Rede) mostra a mesma coisa: uma aba Response vazia significa que o problema está na requisição ou no servidor, e não no seu código de parse.
Quando o texto foi cortado
Se o texto não está vazio, ele terminou antes de o documento ficar completo. O Chrome e o Node.js atuais só dizem "Unexpected end of JSON input" quando o texto para onde deveria começar um valor, ou no meio de true, false ou null. Em qualquer outro ponto, o mesmo corte produz uma mensagem diferente:
| O texto termina com | Mensagem no Chrome e no Node.js atuais |
|---|---|
{"id": | Unexpected end of JSON input |
{"tags":["a", | Unexpected end of JSON input |
{"ok":tru | Unexpected end of JSON input |
{"name":"Al (11 caracteres) | Unterminated string in JSON at position 11 |
{"id":42 (8 caracteres) | Expected ',' or '}' after property value in JSON at position 8 |
A regra prática: se a posição citada na mensagem é igual ao comprimento do texto, o documento foi truncado, e não escrito errado. Versões mais antigas do Chrome e do Node.js também informavam a maioria desses casos como "Unexpected end of JSON input", por isso logs e respostas antigas usam essa redação para truncamentos em geral.
JSON truncado costuma vir de um destes lugares:
- Parse de um stream pedaço por pedaço. Os dados de
res.body.getReader(), de um eventodatado Node.js ou de um socket chegam em pedaços de tamanho arbitrário, e um pedaço isolado raramente é um documento completo. Junte os pedaços e faça o parse uma única vez no final, ou acumule o JSON delimitado por linhas até cada quebra de linha. - Armazenamento pequeno demais. Uma coluna de banco de dados menor que o documento, em um banco configurado para truncar em vez de rejeitar a gravação, guarda a primeira parte e descarta o resto.
- Cópia de algum lugar que encurta textos longos, como um sistema de logs com tamanho máximo de linha.
- Uma conexão encerrada antes da hora em uma resposta sem
Content-Lengthe que não usava transferência em chunks. Quando o tamanho é declarado, a conexão interrompida é informada como erro de rede: no Node.js,res.json()é rejeitado comTypeError: terminated.
Como se proteger no seu código
Leia o corpo como texto, decida o que um corpo vazio significa para aquele endpoint e só então faça o parse. Assim você trata respostas 204, HEAD e 200 vazias em um único lugar e mantém a mensagem de erro útil quando outra coisa der errado:
async function readJson(res) {
const text = await res.text();
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${text.slice(0, 100) || '(empty body)'}`);
}
if (text.trim() === '') return null; // 204, HEAD or an empty 200
try {
return JSON.parse(text);
} catch (err) {
throw new Error(`Invalid JSON (${text.length} chars): ${err.message}`);
}
}
Para valores armazenados, trate vazio e ausente da mesma forma e recorra a um valor padrão:
function loadCart() {
const raw = localStorage.getItem('cart');
if (!raw) return []; // null (missing) or '' (empty)
try {
return JSON.parse(raw);
} catch {
return []; // damaged value: start again
}
}
No servidor, seja consistente: devolva 204 sem corpo e sem tipo de conteúdo JSON, ou devolva um documento JSON pequeno, como {} ou {"deleted": true}. Uma resposta 200 marcada como application/json e com corpo vazio é justamente a combinação que quebra os clientes.
Conferir o texto manualmente
Quando o corpo não está vazio, copie o conteúdo do painel Network ou dos seus logs e cole no validador de JSON. Ele usa o parser do próprio navegador, então mostra a mesma mensagem e a mesma posição que o seu código recebeu, e formata o documento assim que ele fica válido. Se a posição informada está bem no final do que você colou, procure a parte que falta no transporte ou no armazenamento, e não nos dados. Para erros que citam um caractere específico, como o < de uma página HTML, veja o guia Unexpected token in JSON.