Unexpected Token in JSON: o que significa e como resolver
Esse erro significa que o texto que você tentou analisar não é JSON. O token citado na mensagem costuma revelar exatamente o que chegou no lugar dele.
Você chama JSON.parse() ou response.json() e, em vez dos dados, recebe um SyntaxError. O texto da mensagem muda de um navegador para outro, mas o significado é sempre o mesmo: o parser leu o texto desde o início e encontrou um caractere que não pode aparecer naquele ponto em um JSON válido. O caractere citado e a posição informada são as pistas mais úteis que você tem para descobrir o que aconteceu.
O mesmo erro em engines diferentes
As versões mais novas do Chrome e do Node.js mudaram a redação da mensagem, então você vai encontrar as duas formas nos resultados de busca e em logs mais antigos.
| Onde aparece | Mensagem |
|---|---|
| Chrome, Edge, Node.js 20 e posteriores | Unexpected token '<', "<!DOCTYPE "... is not valid JSON |
| Chrome antigo e Node.js 18 | Unexpected token < in JSON at position 0 |
| Firefox | JSON.parse: unexpected character at line 1 column 1 of the JSON data |
| Safari | JSON Parse error: Unrecognized token '<' |
"Position 0" ou "line 1 column 1" quer dizer que o primeiro caractere já estava errado. Isso quase sempre indica que a resposta é outra coisa que não JSON, e não um pequeno erro de digitação dentro de um documento JSON.
A causa mais comum: uma página HTML no lugar do JSON
Um < inesperado na posição 0 é, de longe, a versão mais frequente. O código pediu JSON a uma API, mas o servidor respondeu com uma página HTML, e HTML começa com <!DOCTYPE html> ou <html>. Os motivos típicos são estes:
- URL errada: um erro de digitação ou a falta do prefixo
/apidevolve a página 404 do site. - Erro no servidor: o framework renderiza uma página HTML de erro 500.
- Sessão expirada: a requisição é redirecionada para uma página de login, e o
fetchsegue o redirecionamento sem avisar. - Fallback de single-page app: o servidor de desenvolvimento ou a hospedagem devolve
index.htmlpara qualquer caminho desconhecido, com status 200. - Um proxy, CDN ou firewall no meio do caminho envia a sua própria página HTML de erro ou de verificação.
O jeito mais rápido de confirmar é olhar a resposta bruta antes de fazer o parse:
const res = await fetch('/api/items');
console.log(res.status, res.headers.get('content-type'));
const text = await res.text();
console.log(text.slice(0, 200)); // shows the HTML if that is what came back
No navegador, você também pode abrir o painel Network (Rede) das ferramentas de desenvolvedor, clicar na requisição e conferir a aba Response. Se aparecer HTML, corrija a URL, a autenticação ou o erro do servidor: o código que faz o parse está certo.
Outros tokens e o que eles indicam
| Mensagem atual (mensagem antiga) | O que você tentou analisar | Correção |
|---|---|---|
"undefined" is not valid JSON (Unexpected token u in JSON at position 0) | O valor undefined, por exemplo uma chave que não existe no localStorage ou uma variável sem valor | Confira se o valor existe antes de analisá-lo |
"[object Object]" is not valid JSON (Unexpected token o in JSON at position 1) | Um objeto que já tinha sido analisado, transformado em string pelo próprio JSON.parse | Use o objeto diretamente; não faça o parse duas vezes |
Expected property name or '}' in JSON at position 1 | Aspas simples ou nomes de propriedade sem aspas, como em {'a': 1} ou {a: 1} | Use aspas duplas em todos os nomes de propriedade e em todas as strings |
Expected double-quoted property name in JSON at position 8 | Uma vírgula sobrando no final, como em {"a": 1,} | Remova a vírgula antes da chave que fecha o objeto |
Unexpected token 'N', "{"a": NaN}" is not valid JSON | NaN, Infinity ou undefined dentro dos dados | Substitua por null ou por um número |
| Uma mensagem que cita um token invisível ou aparentemente vazio na posição 0 | Uma marca de ordem de bytes (BOM) no início de um arquivo salvo por alguns editores do Windows | Salve o arquivo como UTF-8 sem BOM ou remova o primeiro caractere |
Fazer o parse de dados que já foram analisados
Bibliotecas costumam entregar objetos prontos. response.json() já devolve um objeto JavaScript, e o axios faz o mesmo em response.data. Passar esse objeto para JSON.parse faz com que ele seja convertido antes na string "[object Object]", que falha na posição 1. A solução é remover o parse extra:
const data = await res.json();
// JSON.parse(data) <- fails: data is already an object
console.log(data.items);
Encontrar o erro dentro de um JSON de verdade
Quando a posição do erro fica em algum ponto no meio do texto, ele é mesmo JSON, só que com um erro. Cole o conteúdo no validador de JSON: ele usa o mesmo parser do seu navegador, mostra a mensagem com a posição e a linha e formata o documento assim que ele fica válido. O erro real costuma estar logo antes da posição informada, como uma vírgula faltando entre dois itens ou uma aspa que deveria ser dupla.
Como se proteger no seu código
Conferir o status e o tipo de conteúdo antes do parse transforma um erro de sintaxe confuso em uma mensagem que diz o que realmente aconteceu:
async function getJson(url) {
const res = await fetch(url);
const type = res.headers.get('content-type') || '';
if (!res.ok || !type.includes('application/json')) {
const body = await res.text();
throw new Error(`Expected JSON from ${url}, got ${res.status} ${type}: ${body.slice(0, 100)}`);
}
return res.json();
}
A mesma ideia vale do lado do servidor: faça as rotas de API devolverem corpos de erro em JSON, e não páginas HTML de erro, e envie junto o cabeçalho Content-Type: application/json. Para a mensagem parecida que aparece quando o corpo da resposta está vazio ou cortado, veja o guia Unexpected end of JSON input.