Leer los límites de la gramática JSON desde RFC 8259
En resumen: Traducir a lenguaje llano las producciones gramaticales de RFC 8259: qué caracteres pueden aparecer directamente, cuáles deben escaparse y dónde deja margen deliberadamente la especificación.

En resumen: cuando aparezca un error de sintaxis JSON, encuentra el primer carácter ilegal y clasifícalo como comilla simple, coma final, comentario o carácter de control en lugar de releer todo el documento; en el lado de la codificación, confirma UTF-8 sin BOM y lleva los enteros por encima de 2^53 - 1 como cadenas.
El cuerpo de la especificación ocupa apenas una docena de páginas, y sin embargo detrás de cada "MUST", "SHOULD" y "MAY" se esconde un incidente que en su día mantuvo a un equipo en vela toda la noche.
1. Cómo se relacionan los tres textos
Aprender JSON a menudo resulta confuso por tres números: RFC 4627, RFC 7159, RFC 8259, además de ECMA-404. La relación es en realidad bastante clara:
| Texto | Fecha | Estado | Diferencia clave |
|---|---|---|---|
| RFC 4627 | 2006 | Reemplazado | El nivel superior debe ser un objeto o un array |
| RFC 7159 | 2014 | Reemplazado | Nivel superior relajado a cualquier valor JSON |
| RFC 8259 | 2017.12 | Estándar vigente | UTF-8 explícito, añadida orientación sobre interoperabilidad |
| ECMA-404 | 2013, segunda edición 2017.12 | Especificación de gramática | Define solo la gramática, no cubre codificación ni transporte |
¿Cuál hay que leer? Con RFC 8259 basta. ECMA-404 es equivalente en su parte gramatical; la diferencia es que RFC 8259 también especifica "transporte como UTF-8" y "los problemas de interoperabilidad a los que deben prestar atención los implementadores".
2. Seis tipos de valor
Las producciones gramaticales de la especificación se pueden condensar en un único diagrama:
JSON-text = ws value ws
value = object / array / number / string / "true" / "false" / "null"
object = "{" [ member *( "," member ) ] "}"
member = string ws ":" ws value
array = "[" [ value *( "," value ) ] "]"
Solo hay seis puntos clave:
- Los objetos usan llaves, los miembros se separan con comas y una coma después del último miembro no está permitida;
- Los arrays usan corchetes, los elementos se separan con comas y las comas finales tampoco están permitidas;
- Los números no admiten
+inicial, ni ceros iniciales (01es inválido), ni punto decimal solo (1.es inválido), niNaNoInfinity; - Las cadenas deben usar comillas dobles; las comillas simples son inválidas;
- Los booleanos y null son literales en minúsculas;
TrueyNULLson ambos inválidos; - Solo los caracteres de espacio
0x20(espacio),0x09(tabulador),0x0A(salto de línea) y0x0D(retorno de carro) pueden aparecer entre elementos estructurales.
El fragmento siguiente es JSON válido, aunque mucha gente cree que está mal la primera vez que lo ve, porque es una cadena de nivel superior desnuda:
"just a string"
Desde RFC 7159, el nivel superior puede efectivamente ser cualquier valor. En las API, no obstante, sigue recomendándose usar solo objetos o arrays; la razón está en la sección 6.
3. Qué puede aparecer realmente de forma directa dentro de una cadena
Esta es la parte que más fácilmente engaña el "en mi máquina funciona". Las reglas de la especificación para las cadenas son:
- Caracteres que pueden aparecer directamente:
%x20-21,%x23-5B,%x5D-10FFFF; - Es decir, la comilla doble
"(0x22) y la barra invertida\(0x5C) deben escaparse; - Todos los caracteres de control por debajo de 0x20 deben escaparse, incluidos salto de línea, tabulador y retorno de carro;
- Las únicas secuencias de escape disponibles son
\",\\,\/,\b,\f,\n,\r,\ty\uXXXX.
{
"ok": "Chinese text, emoji 🎉 and full-width symbols can be written directly",
"escaped": "A line break goes here\nand a tab\tfinishes it",
"control": "\u0007 is the bell character and must be escaped",
"quote": "He said: \"don't use single quotes\""
}
\/ es un escape heredado (evita que </script> se cierre antes de tiempo). Los escenarios modernos rara vez lo necesitan, pero los analizadores deben admitirlo.
4. Codificación: UTF-8, sin BOM
La sección 8.1 de RFC 8259 dice tres cosas:
- El texto JSON usado para intercambio debe codificarse en UTF-8;
- Las implementaciones no deben añadir un BOM (
EF BB BF) al principio del texto; - Los analizadores pueden ignorar el BOM para ser más robustos.
Dos fallos típicos en el mundo real:
- El Bloc de notas de Windows guarda como "UTF-8 con BOM", el archivo es rechazado por
JSON.parseconUnexpected token; - Un archivo JSON chino guardado como GBK se convierte en texto corrupto en una tubería que solo decodifica como UTF-8, y aun así la sintaxis pasa por casualidad, así que el error se difiere a la capa de negocio.
El tratamiento es sencillo: quita el BOM al leer el archivo y después decodifica explícitamente como UTF-8; al escribir, asegúrate de que no se añade ningún BOM.
/**
* Elimina el BOM UTF-8 al principio de una cadena.
* @param text El texto original
* @returns El texto sin BOM
*/
function stripBom(text: string): string {
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
}
Una nota sobre los tipos de medio ya que estamos: application/json ya no acepta un parámetro charset; escribirlo no genera un error, pero es una práctica obsoleta.
5. Tres puntos donde la especificación deja margen deliberadamente
Los puntos donde la especificación se niega a fijar las cosas son exactamente de donde vienen las diferencias entre implementaciones.
1. Rango y precisión de los números
RFC 8259 solo dice que "esta especificación no limita el rango ni la precisión de los números", a la vez que señala que la mayoría de las implementaciones usan la doble precisión de IEEE 754.
Esto significa que el mismo 9223372036854775807 se convierte en 9223372036854776000 en JavaScript, y aun así se mantiene exacto en el long de Java.
Conclusión: los enteros más allá de 2^53 - 1, y los importes monetarios que necesitan muchos decimales de precisión, deben llevarse siempre como cadenas, con el pattern escrito explícitamente en el schema.
2. Claves duplicadas
La redacción de la especificación es "los nombres dentro de un objeto deberían (SHOULD) ser únicos"; no dice qué hacer cuando no lo son.
JSON.parseconserva el último;- Algunos analizadores conservan el primero;
- Algunas bibliotecas simplemente dan error.
Conclusión: no escribas claves duplicadas y no dependas de ningún comportamiento concreto en tus validadores. Añadir un paso de "detección de claves duplicadas" en tiempo de compilación es mucho más barato que investigarlo después.
3. Límites de profundidad y longitud
La especificación no fija ningún límite a la profundidad de anidamiento, pero cada implementación tiene uno. V8 lanza RangeError: Maximum call stack size exceeded con decenas de miles de niveles de anidamiento.
Los servidores deben imponer sus propios límites; esto es un requisito de seguridad, no de la especificación.
6. Comprobar tu comprensión con contraejemplos
Cada línea de abajo es JSON inválido. Pásalas a un analizador y observa si los mensajes de error son útiles:
{ 'name': 'json' } // comillas simples
{ name: "json" } // nombre de clave sin comillas
{"a": 1,} // coma final
[1, 2, 3,] // coma final en un array
{"a": 01} // cero inicial
{"a": +1} // signo más inicial
{"a": .5} // falta la parte entera
{"a": NaN} // literal no estándar
{"a": undefined} // no es un valor JSON
{"a": 1} // comment // comentario
Un caso límite interesante: {"a": 1} seguido de un comentario es válido en JSON5, válido en JSONC e inválido en JSON estándar.
Así que "el archivo de configuración funciona pero la API lo rechaza" es casi siempre un problema de dialecto.
7. Lista de comprobación para producción
- [ ] Todo el JSON transmitido externamente usa
application/jsonsincharset; - [ ] Las tuberías de lectura/escritura de archivos quitan el BOM y usan UTF-8 explícito;
- [ ] Los enteros más allá de
2^53se llevan como cadenas y se anotan en el schema; - [ ] Las claves duplicadas se detectan en tiempo de compilación;
- [ ] El servidor limita el tamaño del cuerpo de la petición y la profundidad de anidamiento;
- [ ] Los valores de nivel superior de las API públicas son objetos o arrays, nunca escalares desnudos.
Preguntas frecuentes
¿Cómo distingo rápidamente si un texto es JSON válido?
Pasa el texto a un analizador o a una herramienta de análisis en línea, lee la posición del primer carácter ilegal y compruébala con los seis tipos de valor y las reglas de espacios de RFC 8259. El nivel superior puede ser cualquier valor JSON, pero entre elementos estructurales solo se permiten espacio, tabulador, salto de línea y retorno de carro. Una comilla simple, una clave sin comillas, una coma final o un comentario significan que no es JSON estándar.
¿Por qué JSON debe usar UTF-8 y no llevar BOM?
La sección 8.1 de RFC 8259 exige que el texto JSON usado para intercambio se codifique en UTF-8, dice que las implementaciones no deben añadir un BOM al principio y permite que los analizadores ignoren el BOM por robustez. Un archivo con BOM es rechazado por JSON.parse con Unexpected token, aunque para el ojo nada parezca mal. Así que quita el BOM al leer y decodifica explícitamente como UTF-8.
¿Debo usar JSON5 o JSONC para sortear estos límites gramaticales?
Sí, pero solo para lectura local, como archivos de configuración; los datos que se intercambian entre sistemas deben seguir siendo JSON estándar. Los contraejemplos muestran que {"a": 1} seguido de un comentario es válido en JSON5 y JSONC pero inválido en JSON estándar, que es por lo que "la configuración funciona pero la API la rechaza" casi siempre es un problema de dialecto. Al convertir, quita primero los comentarios, las comillas simples y las comas finales y entrega el resultado a un analizador estándar.