Unexpected end of JSON input: qué significa y cómo solucionarlo
El analizador se quedó sin texto antes de encontrar un valor completo y, en la mayoría de los casos, es porque no había texto alguno.
Llama a JSON.parse() o a response.json() y obtiene SyntaxError: Unexpected end of JSON input. A diferencia de «unexpected token», este error no señala un carácter incorrecto. Significa que el analizador llegó al último carácter cuando todavía esperaba un valor. O bien el texto estaba vacío, o bien se interrumpió a mitad de un documento. Distinguir esos dos casos es todo el trabajo, y basta con una línea de log.
El mismo error en distintos entornos
| Dónde lo verá | Mensaje |
|---|---|
Chrome, Edge y Node.js, JSON.parse | Unexpected end of JSON input |
Chrome y Edge, response.json() | El mismo, a veces precedido de Failed to execute 'json' on 'Response': |
Chrome, con una respuesta sin cuerpo alguno, como una respuesta 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) |
Cuando un mensaje menciona la línea 1, la columna 1 o el carácter 0, el analizador no encontró nada que leer. Un texto vacío no es JSON válido: los documentos válidos más cortos son valores como 0, null, "" o {}.
La causa habitual: un cuerpo vacío o una cadena vacía
Antes de buscar un fallo en los datos, compruebe si había datos. Estas son las fuentes habituales de una cadena vacía:
- 204 No Content. Muchas API responden a un
DELETEo a unPUTcorrectos con el estado 204 y sin cuerpo.res.okvaletruepara un 204, así que comprobarlo no protege la llamada ares.json()que viene después. - Peticiones HEAD. Una respuesta a
HEADnunca tiene cuerpo, digan lo que digan sus cabeceras. - fetch con
mode: 'no-cors'. Esto no sortea CORS. Devuelve una respuesta opaca con estado 0 y un cuerpo que su código no tiene permiso para leer, de modo que analizarlo falla siempre. Active CORS en el servidor o llame a la API a través de su propio backend. - Un 200 o un 500 sin contenido. Un manejador que nunca escribe su salida, o un error del servidor que termina la petición antes de enviar nada, produce un cuerpo vacío con un código de estado normal y corriente.
- Valores almacenados y archivos vacíos.
localStorage.setItem('cart', ''), unsettings.jsonvacío o una variable de entorno definida pero en blanco entregan aJSON.parseuna cadena vacía. - Un salto de línea final en JSON delimitado por líneas.
text.split('\n').map(JSON.parse)falla con la cadena vacía que queda tras el último salto de línea. Filtre antes las líneas en blanco.
Hay dos casos parecidos que se comportan de otra manera. Una clave de localStorage que no existe devuelve null, y JSON.parse(null) devuelve null sin lanzar ningún error. Una variable sin asignar es undefined, que falla con "undefined" is not valid JSON; ese caso se explica en Unexpected token in JSON.
Para saber cuál de los casos tiene delante, lea el cuerpo como texto antes de analizarlo:
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
En el navegador, el panel Red (Network) muestra lo mismo: si la pestaña Respuesta está vacía, el problema está en la petición o en el servidor, no en su código de análisis.
Cuando el texto se cortó
Si el texto no está vacío, terminó antes de que el documento estuviera completo. Las versiones actuales de Chrome y Node.js solo dicen «Unexpected end of JSON input» cuando el texto se detiene justo donde debería empezar un valor, o en mitad de true, false o null. En cualquier otro punto, el mismo corte produce un mensaje distinto:
| El texto termina en | Mensaje en las versiones actuales de Chrome y Node.js |
|---|---|
{"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 |
La regla útil: si la posición del mensaje es igual a la longitud del texto, el documento se truncó, no está mal formado. Las versiones antiguas de Chrome y Node.js también informaban de la mayoría de estos casos como «Unexpected end of JSON input», por lo que los logs y las respuestas antiguas usan esa redacción para cualquier truncamiento.
El JSON truncado suele venir de uno de estos sitios:
- Analizar un stream fragmento a fragmento. Los datos de
res.body.getReader(), de un eventodatade Node.js o de un socket llegan en trozos de tamaño arbitrario, y un trozo suelto rara vez es un documento completo. Reúna los fragmentos y analice una sola vez al final, o acumule el JSON delimitado por líneas hasta cada salto de línea. - Un almacenamiento demasiado pequeño. Una columna de base de datos más corta que el documento, en una base de datos configurada para truncar en lugar de rechazar la escritura, guarda la primera parte y descarta el resto.
- Copiar desde un lugar que acorta los textos largos, como un sistema de logs con una longitud máxima de línea.
- Una conexión que se cerró antes de tiempo en una respuesta sin
Content-Lengthy sin codificación chunked. Cuando la longitud está declarada, la conexión interrumpida se notifica como un error de red: en Node.js,res.json()se rechaza conTypeError: terminated.
Cómo protegerse en el código
Lea el cuerpo como texto, decida qué significa un cuerpo vacío para ese endpoint y solo entonces analice. Así gestiona en un único lugar las respuestas 204, las de HEAD y los 200 vacíos, y el mensaje de error sigue siendo útil cuando falla otra cosa:
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}`);
}
}
Con los valores almacenados, trate igual el valor vacío y el inexistente, y recurra a un valor por defecto:
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
}
}
En el lado del servidor, sea coherente: devuelva un 204 sin cuerpo y sin tipo de contenido JSON, o devuelva un documento JSON pequeño como {} o {"deleted": true}. Una respuesta 200 etiquetada como application/json con el cuerpo vacío es justo la combinación que rompe a los clientes.
Revisar el texto a mano
Cuando el cuerpo no está vacío, cópielo desde el panel Red o desde sus logs y péguelo en el validador de JSON. Usa el propio analizador del navegador, así que muestra el mismo mensaje y la misma posición que recibió su código, y formatea el documento en cuanto es válido. Si la posición indicada está justo al final de lo que ha pegado, busque la parte que falta en el transporte o en el almacenamiento, no en los datos. Para los errores que nombran un carácter concreto, como el < de una página HTML, consulte Unexpected token in JSON.