Saltar al contenido
jsonbeautifiers
Español

Los diez errores de JSON que de verdad rompen un payload

Ordenados por frecuencia, no por lo interesantes que son, con el mensaje de error exacto que produce cada uno.

Cada afirmación de esta página está medida o tiene fuente. Cuando no es ninguna de las dos, lo dice.

Casi todo ticket de «JSON no válido» es una de diez cosas. El parser te da una posición, a veces un carácter, y nunca la causa. Aquí están en orden aproximado de frecuencia, con el mensaje que habrás visto y el arreglo.

1. Comas finales

El más común con diferencia, porque cualquier otro formato que escribes durante el día las permite.

{ "a": 1, "b": 2, }

V8 te da un mensaje que no menciona ni las comas ni lo que hiciste:

Expected double-quoted property name in JSON at position 18 (line 1 column 19)

El parser consumió la coma, esperó otra clave y se topó con }. Ahora haz lo mismo en un array:

Unexpected token ']', "[1,2,]" is not valid JSON

Redacción completamente distinta para el error idéntico, porque en el parser la ruta de los arrays falla en otra producción de la gramática. Si estás buscando la cadena del error para averiguar qué pasó, esa asimetría es la razón de que no encuentres nada útil.

Python es más directo, pero solo desde hace poco. En 3.13 y posteriores:

Illegal trailing comma before end of object

En 3.12 y anteriores la misma entrada da Expecting property name enclosed in double quotes: line 1 column 19 (char 18). El mismo intérprete, el mismo fallo en tu archivo, dos explicaciones distintas según la versión que tu CI tenga fijada.

Arreglo: borra la coma. { "a": 1, "b": 2 }.

2. Comillas simples

Un dict de Python que pasó por print() o str() en vez de por json.dumps():

{'ok': True}

Eso no es JSON y nunca lo fue. Falla en la primera comilla:

Expected property name or '}' in JSON at position 1 (line 1 column 2)

Fíjate también en True, que es un segundo fallo, distinto, esperando detrás del primero. Los booleanos de JSON van en minúsculas.

{"ok": true}

Arréglalo en el origen: json.dumps(obj), y si la salida va a un archivo UTF-8 o a un cuerpo HTTP, json.dumps(obj, ensure_ascii=False) para que los caracteres acentuados sigan siendo legibles en vez de convertirse en escapes \uXXXX. Añade separators=(",", ":") si lo quieres compacto.

3. Claves sin comillas

Un literal de objeto de JavaScript pegado tal cual en un campo JSON:

{ name: "ada", active: true }
Expected property name or '}' in JSON at position 2 (line 1 column 3)

JSON exige que cada clave sea una cadena entre comillas dobles. Ni comillas simples, ni desnuda, ni un número. {"name": "ada", "active": true}. Es la misma clase de pegado que el anterior y el arreglo es el mismo: saca el valor del runtime con un serializador de verdad y no de un log de consola.

4. Caracteres de control sin escapar

Un salto de línea real dentro de un literal de cadena:

{"note": "line one
line two"}
Bad control character in string literal in JSON at position 18 (line 1 column 19)

Python lo llama Invalid control character at: line 1 column 19 (char 18). En cualquier caso el parser te está diciendo que apareció un carácter por debajo de U+0020 dentro de una cadena, donde solo se admite su escape.

{"note": "line one\nline two"}

Los tabuladores son el mismo problema y más difíciles de ver, porque un tabulador pegado en un valor parece espacios. La causa raíz es casi siempre JSON ensamblado con concatenación de cadenas, donde un campo que contiene un salto de línea se inserta literalmente. La última sección se ocupa de eso como es debido.

5. Rutas de Windows

{"path": "C:\Users\ada\config.json"}

\U y \a no son escapes válidos. Python es explícito: Invalid \escape: line 1 column 13 (char 12). V8 dice Bad escaped character in JSON at position 13 (line 1 column 14), señalando la U en vez de la barra invertida. Los nueve escapes legales son \" \\ \/ \b \f \n \r \t y \uXXXX. Todo lo demás es un error, lo cual es el diseño correcto y sorprende constantemente.

{"path": "C:\\Users\\ada\\config.json"}

Las barras normales funcionan bien en Windows en casi cualquier API, y no te cuestan barras invertidas duplicadas. Si tienes un bloque de texto que incrustar y prefieres no hacerlo a mano, la herramienta de escape lo hace, y desescapar va en sentido contrario.

6. Caracteres invisibles

Este es el que se come una tarde. Dos variantes:

Espacio duro (U+00A0). Copia un fragmento de una página de documentación, un cliente de chat o un PDF y puede que los espacios entre tokens no sean espacios. La RFC 8259 permite exactamente cuatro caracteres de espacio en blanco entre tokens: espacio, tabulador, retorno de carro y salto de línea. U+00A0 no es uno de ellos, así que es un error de sintaxis, y se ve idéntico al carácter que tiene al lado.

Comillas tipográficas. Word y Google Docs autocorrigen la comilla recta U+0022 al par tipográfico U+201C y U+201D mientras escribes. JSON solo acepta U+0022. Un documento que en pantalla parece perfectamente entrecomillado no tiene ningún delimitador de cadena.

Ninguna de las dos variantes produce un mensaje que nombre el punto de código. Según dónde caiga el carácter obtienes Expected double-quoted property name in JSON at position 8, o un Unexpected token ' ' que te imprime de vuelta un carácter que no distingues de un espacio normal. Pega el documento en el validador y te nombra el carácter y su punto de código en el desplazamiento exacto, que es la forma más rápida de encontrarlo. Reparar JSON los elimina y te dice qué quitó.

7. Comentarios

{
  // el nombre visible del usuario
  "name": "ada"
}

V8 reporta Expected property name or '}' in JSON at position 4 (line 2 column 3). Python se detiene con Expecting property name enclosed in double quotes: line 2 column 3 (char 4). Ambos señalan la barra, y ninguno dice la palabra comentario, así que el mensaje se lee como un problema de comillas en una línea que no contiene ninguna cadena.

JSON no tiene sintaxis de comentarios. Crockford la quitó a propósito, porque la gente usaba los comentarios para transportar directivas de parseo. Si controlas al consumidor, JSONC (lo que usa VS Code para su propia configuración) permite comentarios y comas finales, y JSON5 permite bastante más. Si no lo controlas, mueve la prosa a un campo, o al esquema, que es donde van las descripciones. El argumento completo merece diez minutos si estás eligiendo un formato de configuración.

8. NaN e Infinity

{"ratio": NaN}
Unexpected token 'N', "{"ratio": NaN}" is not valid JSON

La trampa es que Python lo emite por defecto. json.dumps({"ratio": float("nan")}) produce {"ratio": NaN} y no lanza nada, porque el codificador de CPython es deliberadamente permisivo y su propio decodificador acepta el valor de vuelta. Todos los consumidores que no son Python lo rechazan.

json.dumps(obj, allow_nan=False)   # lanza ValueError en vez de enviar JSON no válido

Activa eso hoy en tu capa de serialización. Un NaN que llega a producción es una división que no protegiste, y prefieres encontrarla en el codificador antes que en el parser de un cliente.

9. Claves duplicadas

{"id": 1, "id": 2}

Ningún error en absoluto. La RFC 8259 dice que las claves DEBERÍAN ser únicas y deja el comportamiento sin definir cuando no lo son. JavaScript y Python se quedan ambos con la última, así que esto se parsea como {"id": 2} y tu primer valor desaparece sin dejar rastro. Otros parsers se quedan con la primera, y algunos lanzan error. Es el único punto de la lista que es silencioso, lo que lo convierte en el peor. Pasa el payload por el validador, que marca los duplicados en vez de colapsarlos en silencio.

10. Números

Dos fallos comparten esta plaza.

Ceros a la izquierda. {"code": 007} no es válido. La gramática de JSON admite un único 0, o un dígito del 1 al 9 seguido de más dígitos, y nada más. Un código postal, un prefijo de país o un número de pieza con cero a la izquierda es una cadena. {"code": "007"}.

Enteros por encima de 2^53-1. {"id": 12345678901234567890} se parsea sin problemas y vuelve como otro número, porque JavaScript lo almacena como un double IEEE 754 y Number.MAX_SAFE_INTEGER es 9007199254740991. Sin error, sin aviso, registro equivocado. Envía los IDs grandes como cadenas; la versión larga explica por qué cualquier otro arreglo es un parche.

El arreglo estructural

La mitad de esta lista (los puntos 2, 3, 4 y 5) viene de la misma costumbre: producir JSON con algo que no es un serializador, normalmente concatenación de cadenas o un log de consola.

# cada uno de estos es un bug esperando la entrada adecuada
body = '{"note": "' + note + '", "path": "' + path + '"}'

Un salto de línea en note lo rompe. Una barra invertida en path lo rompe. Una comilla en cualquiera de los dos lo rompe, y si esa entrada vino de un usuario es una inyección, no un problema de formato.

body = json.dumps({"note": note, "path": path}, allow_nan=False)

El serializador escapa lo que hay que escapar, entrecomilla lo que hay que entrecomillar y rechaza lo que no se puede representar. No es una preferencia de estilo. Plantillar JSON a mano significa reimplementar correctamente las reglas de escape de la sección 7 de la RFC 8259 en cada rama, y nadie lo hace.

Cuando lo que te entregan es un documento roto y no un productor roto, Reparar JSON aplica los arreglos de arriba e imprime la lista de cada cambio que hizo, para que puedas ver si adivinó algo antes de fiarte del resultado.