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 apareceMensagem
Chrome, Edge e Node.js, JSON.parseUnexpected 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-corsUnexpected end of input
FirefoxJSON.parse: unexpected end of data at line 1 column 1 of the JSON data
SafariJSON Parse error: Unexpected EOF
Python, json.loadsExpecting 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 DELETE ou a um PUT bem-sucedido com status 204 e sem corpo. res.ok é true para 204, então conferir esse valor não protege a chamada res.json() que vem logo depois.
  • Requisições HEAD. Uma resposta a HEAD nunca 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', ''), um settings.json vazio ou uma variável de ambiente definida, mas em branco, entregam uma string vazia ao JSON.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 comMensagem no Chrome e no Node.js atuais
{"id":Unexpected end of JSON input
{"tags":["a",Unexpected end of JSON input
{"ok":truUnexpected 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 evento data do 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-Length e 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 com TypeError: 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.

Perguntas Frequentes

O que significa "Unexpected end of JSON input"?

O parser chegou ao fim do texto enquanto ainda esperava um valor. Normalmente o texto estava vazio, por exemplo em uma resposta 204 ou em um valor armazenado vazio. Às vezes o documento foi cortado no meio.

Por que response.json() falha se a requisição deu certo?

Sucesso não significa que existe um corpo. Uma resposta 204 No Content tem ok igual a true e nenhum corpo. Leia o corpo com res.text() e só faça o parse quando ele não estiver vazio.

JSON.parse(null) lança esse erro?

Não. null é convertido no texto "null", que é JSON válido, então JSON.parse(null) devolve null. Uma string vazia lança "Unexpected end of JSON input", e undefined lança um erro diferente.

Como diferenciar um corpo vazio de um corpo truncado?

Registre o comprimento do texto antes do parse. Zero significa vazio. Se o comprimento for maior que zero e a posição do erro for igual a ele, o documento foi cortado.