"Unexpected end of JSON input" — el cuerpo venía vacío o cortado
Este error significa que al parser se le acabó el texto antes de completar la estructura JSON. Casi siempre el cuerpo de la respuesta venía vacío, o la transferencia se cortó — y cada caso tiene una solución distinta.
A diferencia de la mayoría de errores de JSON, este no menciona ningún carácter, porque no quedaba ninguno que mencionar. El parser llegó al final de la entrada mientras seguía esperando una llave, un corchete o una comilla de cierre. Tu código de parseo está bien — te entregaron menos texto del que forma un documento JSON completo.
La causa más común: el cuerpo venía completamente vacío
JSON.parse("") lanza exactamente este error, y una cadena vacía es algo que aparece más a menudo de lo que cabría esperar. Una respuesta 204 No Content no tiene cuerpo por definición, y es lo normal en un DELETE o un PUT correctos. Un 304 Not Modified tampoco devuelve nada, porque justamente el mensaje es que tu caché sigue siendo válida. Una petición HEAD nunca devuelve cuerpo. Y una petición que falló a nivel de red — conexión rechazada, preflight de CORS denegado, petición abortada — puede dejarte una cadena vacía en vez de una excepción, según el cliente.
La segunda causa: la respuesta se cortó a mitad de camino
Aquí el cuerpo empieza siendo JSON válido y simplemente se detiene a medias. La conexión se cayó, saltó un timeout mientras la respuesta seguía llegando, o algo entre tú y el servidor impuso un límite de tamaño — los proxies inversos, la configuración de buffers de nginx y los topes de respuesta de las funciones serverless hacen esto en silencio. Una señal útil es que el corte suele ser intermitente y guardar relación con el tamaño: las respuestas pequeñas funcionan y las grandes fallan. Si el mismo endpoint parsea bien con un registro y se rompe con mil, estás ante un truncamiento, no ante un JSON mal formado.
La tercera causa: leer de almacenamiento o de un archivo
Si el JSON viene de localStorage, de un archivo de caché o del disco, puede que lo incompleto fuera la escritura. localStorage falla en silencio en cuanto se supera la cuota, así que un objeto grande puede quedar escrito a medias sin que se lance ninguna excepción al escribir — el error solo aparece después, cuando algo intenta leerlo. Un archivo escrito por un proceso que se cayó, o en un disco que se llenó, produce lo mismo. Lo que lo distingue es que falla exactamente igual siempre, mientras que un corte de red suele ser intermitente.
Cómo distinguirlos en un solo paso
Antes de llamar a JSON.parse, imprime la longitud del valor en crudo y sus últimos caracteres. Una longitud cero significa cuerpo vacío, y lo que deberías estar haciendo es tratar el código de estado en vez de parsear. Una longitud distinta de cero que no termina en } o en ] significa truncamiento, y toca comparar la longitud recibida con la cabecera Content-Length. Si la longitud parece completa y aun así falla, el problema está en otra parte de la cadena y el mensaje habría nombrado un carácter.
La solución es una guarda, no un try/catch
El instinto es envolver el parseo en un try/catch y seguir adelante, pero eso oculta la condición real y convierte un error claro en un estado vacío silencioso. Comprueba primero el código de estado y trata 204 y 304 explícitamente como "sin cuerpo, y es correcto". Verifica que la respuesta fue realmente satisfactoria antes de dar por hecho que hay algo que parsear. Un try/catch tiene sentido alrededor de datos genuinamente inesperados y mal formados, no alrededor del caso perfectamente previsible de una respuesta que nunca debió contener JSON.
Cuando tengas el texto en crudo y quieras saber exactamente dónde deja de ser válido, pégalo en nuestro validador de JSON — indica la posición en la que la estructura se interrumpe, lo que hace evidente un truncamiento al instante. Funciona en tu navegador, así que puedes pegar una respuesta real de una API sin riesgo.