Cómo solucionar “Unexpected token” y otros errores de JSON.parse

“Unexpected token in JSON” significa que el parser recibió algo distinto del JSON esperado. Puede ser un solo carácter incorrecto, pero también una página HTML, una respuesta vacía, datos truncados o el endpoint equivocado. Lo útil es mirar el texto real que recibió el parser, localizar el fallo y corregirlo en su origen, en lugar de envolver la llamada en un `try/catch` que lo esconda.

Qué significa realmente un error de JSON.parse

JSON.parse() convierte texto JSON válido en un valor de JavaScript. Si el texto rompe la gramática, lanza un SyntaxError. Chrome y Node.js indican el token y la posición, y en versiones recientes también la línea y la columna; Firefox describe el carácter que esperaba, con línea y columna. Todos dicen lo mismo: el punto donde el parser ya no podía seguir.

El token señalado no siempre es la causa. Una comilla que falta hace que una coma posterior parezca inesperada. Unexpected end of JSON input significa que el texto se acabó cuando aún faltaba cerrar una comilla, un corchete o una llave, o faltaba un valor. Mira siempre también lo que hay antes de la posición.

Inspecciona primero la respuesta sin procesar

En una petición con fetch, cambia temporalmente response.json() por response.text() y registra el status, el Content-Type y el principio del cuerpo. No llames a text() y a json() sobre la misma respuesta: el cuerpo es un stream y solo se puede leer una vez. Si necesitas leerlo dos veces, usa response.clone().

Si el primer carácter es <, has recibido HTML: una página de login, un 404, un error del framework o del proxy. Una N al principio puede ser un Not Found en texto plano, y una cadena vacía no tiene nada que analizar. En estos casos no toques la puntuación en JavaScript: revisa la ruta, la autenticación y lo que devuelve el servidor.

Aprovecha la posición, línea y columna

En Unexpected token } in JSON at position 137, la posición es un índice, contado desde cero, dentro del texto que recibió el parser. En lugar de leer entero un payload minificado, imprime una ventana alrededor: text.slice(117, 157). Si prefieres línea y columna, pega el cuerpo en un validador o en un editor que las muestre.

Si el error está al final, vuelve al último campo o elemento completo. Una descarga cortada, una escritura a medias o una concatenación mal hecha se comen el cierre. Contar llaves ayuda poco, porque también pueden aparecer dentro de los strings; un validador sí entiende las comillas y los escapes.

Comas finales, comillas y nombres de propiedades

JSON es más estricto que un objeto literal de JavaScript: propiedades y strings van entre comillas dobles, y no admite comillas simples, claves sin comillas ni una coma después del último elemento. {"name":"Ada",} falla por la coma y {name:"Ada"} por la clave.

No arregles respuestas ajenas con expresiones regulares que cambian comillas o borran comas: tarde o temprano estropean texto válido dentro de algún string. Corrige el serializador o los datos de origen. En el backend, construye una estructura nativa y pásala por el encoder JSON de la plataforma, sin concatenar fragmentos de texto.

Strings, escapes, números y valores no admitidos

Dentro de un string hay que escapar las comillas y las barras invertidas. Un salto de línea o un tabulador literales no están permitidos: se escriben \n y \t. La barra invertida solo puede iniciar uno de estos escapes: \", \\, \/, \b, \f, \n, \r, \t o \u seguido de cuatro dígitos hexadecimales. Las rutas de Windows y el texto multilínea son las fuentes habituales de este error.

Los números JSON no admiten signo +, ceros a la izquierda como 01, un punto decimal final como 1., NaN ni Infinity. Tampoco existen undefined, los comentarios, las funciones ni un tipo de fecha. Usa null u omite la propiedad, y representa las fechas como strings en un formato acordado, por ejemplo ISO 8601. true, false y null van en minúsculas.

Por qué una API devuelve HTML, vacío o JSON truncado

Si aparece <, comprueba la URL, el status y las redirecciones. Una SPA puede devolver index.html para cualquier ruta desconocida, una sesión caducada puede redirigir al login y un proxy inverso puede generar su propia página de error. Un host equivocado o la falta de Accept: application/json también pueden hacer que el servidor elija HTML.

Un cuerpo vacío es legítimo en un 204 No Content, y también aparece con HEAD o con un 304. No intentes analizar una cadena vacía. Un JSON truncado apunta a una excepción en mitad de un streaming, a una conexión cortada o a un límite del proxy; si termina a mitad de un array o un objeto, revisa los logs del servidor y del proxy.

Un patrón fetch más seguro

Comprueba response.ok y el Content-Type antes de fiarte del cuerpo. Lee el texto una sola vez, trata el caso vacío y haz el JSON.parse dentro de un try/catch. Cuando falle, registra la URL, el status, el content type y un fragmento corto y saneado del cuerpo, nunca tokens ni datos personales completos.

El Content-Type es una pista, no una garantía: hay servidores que etiquetan HTML como application/json y JSON como text/plain. Si el contrato se rompe, que el error lo diga con claridad. Para operaciones sin contenido, devuelve null o un resultado propio de tu dominio; para todo lo demás, exige JSON válido.

Valida el contrato y evita regresiones

Que la sintaxis sea válida solo significa que el texto se puede analizar. No garantiza que user.id exista, que price sea un número o que status tenga un valor permitido. Después del parse, valida las respuestas importantes con un schema o con comprobaciones en tiempo de ejecución.

Cubre en los tests el JSON correcto, los errores estructurados, las redirecciones por sesión caducada, el 204 y los cuerpos malformados o truncados. En el servidor, usa un formato de error uniforme, el status y el Content-Type correctos, y convierte las excepciones no controladas de las rutas de API en respuestas JSON.

Checklist rápido de diagnóstico

En orden: confirma la URL y el método; mira el status, las redirecciones y el Content-Type; lee el cuerpo como texto una sola vez; revisa el principio y el final; examina la zona de la posición y valida el payload. Si es HTML o está vacío, el problema está en el endpoint. Si es casi JSON, corrige el productor, el serializador o el registro de origen.

Antes de dar el problema por resuelto, prueba también las respuestas de error, la sesión caducada y los resultados vacíos. No se trata de silenciar JSON.parse, sino de que cliente y servidor compartan el mismo contrato para cada respuesta.

Frequently asked questions

¿Qué causa “Unexpected token < in JSON at position 0”?

Casi siempre, que la respuesta empieza por HTML y no por JSON. Comprueba la URL, el status, las redirecciones, la autenticación y el cuerpo en bruto con `response.text()`: suele ser un 404, una página de login o un error del proxy.

¿Cómo soluciono “Unexpected end of JSON input”?

Busca un cuerpo vacío o cortado. Una respuesta `204` no tiene cuerpo y no debe pasarse a `JSON.parse`; si el JSON se corta a mitad, revisa los logs del servidor y del proxy.

¿JSON admite comillas simples o comas finales?

No. Los strings y los nombres de propiedad usan comillas dobles, y el último elemento de un objeto o array no lleva coma. JSON5 y JSONC sí las admiten, pero `JSON.parse` no.

¿Debo usar try/catch con JSON.parse?

Sí, en cualquier frontera con datos que no controlas, junto con la comprobación del status y del `Content-Type`. Lo que no debes hacer es tragarte el error y sustituir los datos rotos por un objeto vacío sin avisar.

¿JSON válido significa una respuesta API válida?

No. La sintaxis puede ser correcta y aun así faltar campos, venir tipos equivocados o valores fuera de rango. Valida la estructura con un schema o con comprobaciones explícitas del contrato de la API.

¿Qué significa Unexpected non-whitespace character after JSON?

Que después de un JSON completo y válido hay más texto, por ejemplo dos objetos seguidos o una coma sobrante al final. Si son varios registros, envíalos dentro de un array o usa JSON Lines y lee cada línea por separado.