FAQ de sintaxis y formatos: comentarios, comas finales y precisión
En resumen: Si se admiten comentarios, por qué falla una coma final, si las claves necesitan comillas, por qué 9007199254740993 cambia: doce conclusiones, cada una contrastada con RFC 8259.

En resumen: escribe JSON plano de RFC 8259 — comillas dobles, sin comentarios, sin comas finales, UTF-8 sin BOM — para todo lo que cruce una frontera entre procesos; usa JSONC o JSON5 para configuración que necesite comentarios y conviértela de vuelta en tiempo de ejecución; y serializa en el servidor como cadenas los enteros superiores a 2^53-1.
Qué tienen en común estas preguntas: parecen cosas que «deberían funcionar», pero la especificación es en realidad muy estricta.
1. Qué cuenta como JSON válido
¿Cuál es la diferencia entre JSON, JSON5 y JSONC?
El JSON estándar solo reconoce cadenas entre comillas dobles y no admite ni comentarios ni comas finales. JSON5 y JSONC añaden funciones sobre esa base:
| Característica | JSON (RFC 8259) | JSONC | JSON5 |
|---|---|---|---|
| Cadenas entre comillas dobles | Admitidas | Admitidas | Admitidas |
| Cadenas entre comillas simples | No admitidas | No admitidas | Admitidas |
| Comentarios | No admitidos | Admitidos | Admitidos |
| Comas finales | No admitidas | No admitidas | Admitidas |
| Claves sin comillas | No admitidas | No admitidas | Admitidas |
| Números hexadecimales | No admitidos | No admitidos | Admitidos |
NaN / Infinity |
No admitidos | No admitidos | Admitidos |
| Extensión de archivo | .json |
.jsonc |
.json5 |
El principio de elección es simple: usa siempre JSON estándar al cruzar fronteras entre procesos, mientras que los archivos de configuración pueden usar JSONC o JSON5 según lo que admita tu editor.
tsconfig.json y .vscode/settings.json pueden contener comentarios porque el editor los analiza como JSONC; pasarlos por un
JSON.parse estricto sigue dando error.
¿Puede JSON contener comentarios?
No. La gramática de RFC 8259 no tiene producción de comentario, así que cualquier // o /* */ hace fallar el análisis.
Si quieres mantener explicaciones en la configuración, hay tres alternativas:
{
"_comment": "以下超时单位是毫秒",
"timeout": 3000,
"retry": {
"$doc": "重试次数,0 表示不重试",
"count": 3
}
}
- Usar campos convencionales como
_commentque el lector simplemente ignora; - Usar campos con prefijo
$para reducir la probabilidad de colisión con campos de negocio; - Cambiar simplemente a JSONC o YAML para la configuración y convertir a JSON estándar en tiempo de ejecución.
¿Por qué una coma final provoca un error?
Porque en la gramática tanto los arrays como los objetos son estructuras de «elemento + separador», y no se permite ninguna coma después del último elemento.
{
"a": 1,
"b": 2,
}
El fragmento anterior tiene una coma de más antes de }, y el error se informa normalmente en la coma o en la llave de cierre según la implementación del analizador.
También es la trampa más fácil al migrar desde JavaScript: los literales de objeto de JS permiten comas finales, JSON no.
¿Se admiten comillas simples?
No. Las cadenas deben usar comillas dobles; 'text' es un error de sintaxis.
Si los datos se copiaron de repr() de Python, de un script de shell o de configuración escrita a mano, conviértelos primero con una herramienta
en lugar de esperar que el analizador sea indulgente.
¿Pueden ir las claves sin comillas?
No. Los nombres de los miembros de un objeto deben ser cadenas entre comillas dobles.
{ name: "json" }
Eso es un literal de objeto de JavaScript, no JSON. Añadir comillas lo hace válido:
{ "name": "json" }
¿Puede el nivel superior ser un array o una cadena?
Sí. RFC 8259 permite cualquier valor JSON en el nivel superior: objetos, arrays, cadenas, números, true, false y null son todos válidos.
En la práctica de ingeniería, sin embargo, se recomienda usar solo objetos o arrays en el nivel superior, por dos razones:
- Compatibilidad: los analizadores antiguos (de la época de RFC 4627) exigen que el nivel superior sea un objeto o un array;
- Seguridad: un array en el nivel superior ha acarreado históricamente un riesgo de JSON hijacking. Los navegadores modernos lo han corregido, pero los sistemas antiguos siguen asumiendo la convención.
Así que "hello" es JSON válido y ["a", "b"] también, pero una API que devuelve {"data": [...]} es más segura.
2. Codificación y cadenas
¿Un BOM al principio del archivo rompe el análisis?
Sí. RFC 8259 exige explícitamente que el texto JSON se transmita como UTF-8 y no debe añadir una marca de orden de bytes (BOM).
El BOM son los tres bytes EF BB BF al principio mismo del archivo. Es completamente invisible, pero ocupa la primera posición de la cadena,
así que el analizador concluye que «el primer carácter no es {» y da error — y la línea y columna que informa suelen ser línea 1, columna 1, lo que resulta desconcertante.
La opción «Guardar como UTF-8» del Bloc de notas de Windows incluye un BOM de forma predeterminada. Para comprobarlo y eliminarlo:
# Comprobar: una salida que empieza por efbbbf indica que hay un BOM
head -c 3 data.json | xxd
# Eliminar: saltar los 3 primeros bytes con tail
tail -c +4 data.json > clean.json
El JSON.parse de JavaScript moderno tolera un BOM (V8 lo salta internamente), pero las implementaciones estrictas de Java, Go y Python no lo hacen,
lo que es una razón habitual de que «el mismo archivo dé resultados distintos en lenguajes distintos».
¿Hay que convertir los caracteres chinos a \uXXXX?
No. RFC 8259 exige que el texto JSON sea UTF-8, así que el chino puede aparecer directamente; {"name": "张三"} es perfectamente válido.
\uXXXX es solo una forma alternativa equivalente, usada para canales que no pueden representar el carácter directamente. La regla práctica:
- Cuerpos de petición HTTP, archivos, bases de datos: escribe el chino directamente, ahorrando 3 bytes por carácter;
- Cabeceras HTTP, parámetros de URL, incrustar dentro de otra cadena: usa escapes para evitar problemas de codificación.
Los únicos caracteres que deben escaparse son los caracteres de control (U+0000 a U+001F). Por ejemplo, un salto de línea debe escribirse como \n;
no puedes poner un salto de línea literal dentro de una cadena.
¿Pueden los emoji ir directamente en una cadena?
Sí, pero recuerda que ocupan dos unidades de código UTF-16.
Los emoji y muchos caracteres poco comunes (como 𠮷) quedan fuera del BMP, así que en JavaScript "😀".length === 2.
Validar por número de caracteres es, por tanto, fácil de equivocar:
const text = "😀";
console.log(text.length); // 2, número de unidades de código UTF-16
console.log([...text].length); // 1, número de puntos de código
console.log(Buffer.byteLength(text)); // 4, número de bytes UTF-8
Si un límite de longitud de apodo se calcula con .length, un emoji consume dos espacios; si el backend cuenta bytes, son cuatro.
Al alinear definiciones, deja claro si te refieres a puntos de código, unidades de código o bytes.
3. Números y valores especiales
¿Por qué el entero grande 9007199254740993 se ha convertido en 9007199254740992?
Porque el JSON.parse de JavaScript convierte los números en flotantes de doble precisión IEEE 754, y el mayor entero seguro es 2^53-1.
Number.MAX_SAFE_INTEGER === 9007199254740991 // true
9007199254740993 // en realidad da 9007199254740992
Cualquier número por encima de 2^53-1 puede redondearse en sus últimas cifras. Víctimas típicas son los IDs de usuario de 19 dígitos generados por algoritmos snowflake, los números de pedido y las marcas de tiempo en nanosegundos.
Tres soluciones, ordenadas por preferencia:
- Serializar el ID como cadena en el servidor:
"id": "9007199254740993", eliminando el problema de precisión de raíz; - Usar una biblioteca de análisis sin pérdida: los sustitutos de
JSON.parsepueden conservar los enteros grandes tal cual, por ejemplojson-bigint; - Extraer con una expresión regular solo para mostrar: no analices todo el documento, limítate a buscar el campo objetivo como texto.
Ten en cuenta que JSON.stringify tiene el problema simétrico: un número fuera del rango de enteros seguros puede no conservar su valor original después de serializarse.
¿Pueden NaN e Infinity ir en JSON?
No. La producción de número de JSON solo permite notación decimal, y NaN, Infinity y -Infinity no son valores JSON válidos.
El enfoque de JavaScript es la «degradación silenciosa»:
JSON.stringify({ value: NaN }); // '{"value":null}'
JSON.stringify({ value: Infinity }); // '{"value":null}'
Sin error, NaN simplemente se convierte en null. Es el escenario clásico de «el frontend envió NaN y el backend recibió null».
Cuando necesites transmitir esa semántica, exprésala explícitamente con cadenas o valores centinela acordados:
{ "ratio": null, "ratioState": "not-a-number" }
¿Pueden escribirse los números como 1e3 o 0.5?
Sí. Los números JSON admiten decimales y exponentes, así que 1e3, 1E3, 0.5 y -0.5 son todos válidos.
Pero hay restricciones:
- No pueden empezar ni terminar con
.:.5y5.son ambos inválidos, así que escribe0.5y5; - Sin ceros a la izquierda:
01es inválido; - Sin hexadecimal:
0x1Fes inválido, es una extensión de JSON5; -0es válido, peroJSON.stringify(-0)produce0, perdiendo el signo.
4. Semántica de los objetos
¿Qué ocurre con las claves duplicadas?
El comportamiento está definido por la implementación; RFC 8259 solo dice que los nombres de clave «deberían» ser únicos y no especifica qué ocurre con los duplicados.
En la práctica, la gran mayoría de las implementaciones toma el último valor:
JSON.parse('{"a":1,"a":2}') // { a: 2 }
Pero no puedes confiar en ello. Algunas bibliotecas de Java, el encoding/json de Go y el json de Python difieren entre versiones:
unas toman el primer valor, otras dan error y otras toman el último. Tratar las claves duplicadas como error es el enfoque más seguro entre lenguajes.
No puedes encontrar claves duplicadas a ojo: en JSON minificado la misma clave puede estar a miles de caracteres de distancia. Usa un script:
# Extraer las claves que aparecen más de una vez en el mismo nivel, como comprobación aproximada
jq -r 'paths(scalars) | join(".")' data.json | sort | uniq -d
¿En qué se convierte undefined?
Depende, y es el punto más propenso a errores:
JSON.stringify({ a: undefined }); // '{}' la clave se elimina por completo
JSON.stringify({ a: undefined, b: 1 }); // '{"b":1}'
JSON.stringify([undefined]); // '[null]' se convierte en null dentro de los arrays
JSON.stringify({ a: function () {} }); // '{}' las funciones también se eliminan
El mismo undefined significa «el campo desaparece» dentro de un objeto y «se convierte en null» dentro de un array.
Si el backend toma ramas distintas para «falta el campo» y «el campo es null», aquí obtienes una inconsistencia.
Cuando necesites expresar «sin valor» explícitamente, usa null en lugar de undefined.
¿Qué ocurre tras analizar {"__proto__": {...}}?
El propio JSON.parse es seguro: __proto__ se trata como una propiedad propia ordinaria y no cambia el prototipo.
Lo peligroso es la operación de fusión después del análisis:
const patch = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, patch); // algunas implementaciones contaminan el prototipo
El enfoque correcto es iterar sobre las propiedades propias y omitir __proto__, constructor y prototype.
Los detalles están en la FAQ de rendimiento y seguridad.
5. Referencia rápida
| Sintaxis | ¿Válida? | Notas |
|---|---|---|
{"a": 1,} |
No | Coma final |
{'a': 1} |
No | Comillas simples |
{a: 1} |
No | Clave sin comillas |
{"a": 1} // comment |
No | Comentarios |
"hello" |
Sí | Una cadena en el nivel superior es válida, pero se usa poco en APIs |
{"a": 1e3} |
Sí | Se admiten exponentes |
{"a": 01} |
No | Cero a la izquierda |
{"a": NaN} |
No | Literal inválido; stringify lo convierte en null |
\uFEFF{"a":1} |
No | Un BOM no forma parte del texto JSON |
{"a":1,"a":2} |
Discutido | Las implementaciones toman el último valor, pero no confíes en ello |