Unexpected token in JSON: qué significa y cómo solucionarlo
Este error indica que el texto que intentó analizar no es JSON. El token que menciona suele revelar exactamente qué recibió en su lugar.
Llama a JSON.parse() o a response.json() y, en lugar de datos, obtiene un SyntaxError. La redacción cambia según el navegador, pero el significado es siempre el mismo: el analizador leyó el texto desde el principio y encontró un carácter que no puede aparecer en ese punto de un JSON válido. El carácter que cita y la posición que indica son las pistas más útiles de las que dispone.
El mismo error en distintos motores
Las versiones recientes de Chrome y Node.js cambiaron la redacción del mensaje, así que encontrará las dos variantes tanto en los resultados de búsqueda como en los logs antiguos.
| Dónde lo verá | Mensaje |
|---|---|
| Chrome, Edge, Node.js 20 y posteriores | Unexpected token '<', "<!DOCTYPE "... is not valid JSON |
| Versiones antiguas de Chrome y 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» o «line 1 column 1» significa que ya el primer carácter era incorrecto. Eso casi siempre indica que la respuesta es otra cosa distinta de JSON, y no una pequeña errata dentro de un documento JSON.
La causa habitual: una página HTML en lugar de JSON
Un < inesperado en la posición 0 es, con diferencia, la variante más frecuente. El código pidió JSON a una API, pero el servidor respondió con una página HTML, y el HTML empieza por <!DOCTYPE html> o por <html>. Estos son los motivos típicos:
- Una URL incorrecta: una errata o la falta del prefijo
/apidevuelve la página 404 del sitio. - Un error del servidor: el framework genera una página de error 500 en HTML.
- Una sesión caducada: la petición se redirige a una página de inicio de sesión, y
fetchsigue la redirección sin avisar. - El comodín de una aplicación de página única: el servidor de desarrollo o el hosting devuelve
index.htmlpara cualquier ruta desconocida, con estado 200. - Un proxy, una CDN o un firewall intermedio envía su propia página HTML de error o de verificación.
La forma más rápida de confirmarlo es mirar la respuesta en bruto antes de analizarla:
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
En el navegador también puede abrir el panel Red (Network) de las herramientas para desarrolladores, hacer clic en la petición y revisar la pestaña Respuesta. Si ve HTML, corrija la URL, la autenticación o el error del servidor: el código que analiza el JSON está bien.
Otros tokens y a qué apuntan
| Mensaje actual (mensaje antiguo) | Lo que se analizó | Solución |
|---|---|---|
"undefined" is not valid JSON (Unexpected token u in JSON at position 0) | El valor undefined, por ejemplo una clave de localStorage que no existe o una variable sin asignar | Compruebe que el valor existe antes de analizarlo |
"[object Object]" is not valid JSON (Unexpected token o in JSON at position 1) | Un objeto que ya estaba analizado y que JSON.parse convirtió en cadena | Use el objeto directamente; no lo analice dos veces |
Expected property name or '}' in JSON at position 1 | Comillas simples o claves sin comillas, como en {'a': 1} o {a: 1} | Ponga comillas dobles en todas las claves y cadenas |
Expected double-quoted property name in JSON at position 8 | Una coma final, como en {"a": 1,} | Elimine la coma que precede a la llave de cierre |
Unexpected token 'N', "{"a": NaN}" is not valid JSON | NaN, Infinity o undefined dentro de los datos | Sustitúyalos por null o por un número |
| Un mensaje que cita en la posición 0 un token invisible o aparentemente vacío | Una marca de orden de bytes (BOM) al principio de un archivo guardado con algunos editores de Windows | Guarde el archivo como UTF-8 sin BOM, o elimine el primer carácter |
Analizar datos que ya están analizados
Muchas bibliotecas le entregan objetos ya listos para usar. response.json() ya devuelve un objeto de JavaScript, y lo mismo hace axios en response.data. Si pasa ese objeto a JSON.parse, primero se convierte en la cadena "[object Object]", que falla en la posición 1. Elimine el análisis sobrante:
const data = await res.json();
// JSON.parse(data) <- fails: data is already an object
console.log(data.items);
Encontrar el fallo dentro de un JSON real
Cuando la posición del error cae en algún punto intermedio, el texto sí es JSON, pero contiene un fallo. Péguelo en el validador de JSON: usa el mismo analizador que su navegador, muestra el mensaje con su posición y su línea, y formatea el documento en cuanto es válido. El error real suele estar justo antes de la posición indicada, por ejemplo una coma que falta entre dos elementos o una comilla que debería haber sido doble.
Cómo protegerse en el código
Comprobar el código de estado y el tipo de contenido antes de analizar convierte un error de sintaxis confuso en un mensaje que explica lo que ocurrió de verdad:
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();
}
La misma idea se aplica en el lado del servidor: haga que las rutas de la API devuelvan cuerpos de error en JSON en lugar de páginas de error HTML, y envíelos con Content-Type: application/json. Para el mensaje, muy parecido, que aparece cuando el cuerpo está vacío o cortado, consulte Unexpected end of JSON input.