Modo Estricto y Cumplimiento de RFC 8259 en la Validación JSON
Por Aerisium Core · ·
La gramática JSON definida en RFC 8259 es un subconjunto estricto de la sintaxis de object literals de JavaScript. Ambas se ven casi idénticas, pero las diferencias causan sistemáticamente fallos en producción: datos que se analizan sin error en el navegador de un desarrollador rompen silenciosamente consumidores posteriores que ejecutan parsers conformes. Entender exactamente dónde divergen las gramáticas es crítico para construir pipelines de datos fiables.
Object Literals de JavaScript frente a la Gramática JSON
La sintaxis de objetos de JavaScript evolucionó a través de las especificaciones ECMAScript, acumulando comodidades que JSON excluye deliberadamente. RFC 8259 formaliza una gramática restringida sin sintaxis opcional.
Las incompatibilidades críticas son:
-
Trailing commas: JavaScript permite
{ "a": 1, "b": 2, }. La gramática JSON rechaza cualquier coma después del último par clave-valor. El parser espera una coma solo entre pares: tras el último par, el siguiente token debe ser la llave de cierre. -
Cadenas con comillas simples: JavaScript acepta
'key'y"key"indistintamente. La sección 7 de RFC 8259 exige que las cadenas se delimiten exclusivamente con comillas U+0022. Las comillas simples (U+0027) no tienen significado en la gramática léxica de JSON. -
Claves sin comillas: JavaScript permite
{ key: "value" }dondekeyse trata como un identificador. La gramática JSON exige que cada clave sea una cadena entre comillas. -
Comentarios: Los objetos de JavaScript pueden contener comentarios
//y/* */. La gramática JSON no tiene producción de comentarios. La barra inclinada (/) aparece solo en secuencias de escape. -
Undefined y NaN:
JSON.stringifyde JavaScript elimina silenciosamente los valoresundefinedy convierteNaNennull. Un parser que acepte estos valores como entrada produce datos que no sobreviven un round-trip a través deJSON.parse.
Trailing Commas en Pipelines de Producción
La aceptación de trailing commas es el fallo de validación JSON más común en sistemas reales. Considérese un archivo de configuración consumido por un procesador de flujos basado en Java:
{
"queue_batch_size": 1000,
"retry_policy": "exponential_backoff",
"dead_letter_topic": "dlq.primary",
}
La trailing comma después de "dead_letter_topic" hace que cualquier parser compatible con RFC 8259, como com.google.gson.stream.JsonReader o com.fasterxml.jackson.core.JsonParser, lance una excepción de parseo. El procesador de flujos falla al iniciar. El despliegue fracasa. El incidente cuesta horas de ingeniería para diagnosticar porque el mismo JSON se renderiza sin error en todos los formateadores basados en navegador que toleran trailing commas.
La causa raíz es un desajuste entre las herramientas de desarrollo y el parseo en producción. Los desarrolladores que trabajan en el navegador nunca ven el error porque JSON.parse() de V8 ha sido estrictamente compatible con RFC desde ES5 y también rechaza trailing commas. La desconexión ocurre cuando herramientas intermedias —editores, formateadores o scripts personalizados— aceptan silenciosamente la entrada malformada antes de que los datos lleguen al parser de producción. Cuando el parser estricto la rechaza, el artefacto ya se ha desplegado.
Validación en Streaming frente a Construcción de AST
Validar un documento JSON contra RFC 8259 puede realizarse en dos niveles, cada uno con complejidad computacional distinta.
Un lexer en streaming se ejecuta en tiempo O(n) y memoria O(1). El parser clasifica cada caracter secuencialmente: los espacios en blanco se descartan, los tokens estructurales ({, }, [, ], :, ,) se validan en las posiciones esperadas, las cadenas se escanean en busca de caracteres de control sin escapar, y los números se verifican contra la gramática numérica (-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?). El lexer rechaza la entrada malformada en el primer carácter inválido y termina inmediatamente.
La construcción completa de AST requiere espacio O(n + m), donde m es el número de nodos asignados. Un documento profundamente anidado como {"a":{"a":{"a":{"a":null}}}} asigna un nuevo descriptor de objeto en el heap de V8 por cada nivel de anidamiento. Un array plano de 10 MB de primitivas produce un AST pequeño: un nodo array más N nodos primitivos. Un documento de 10 MB de objetos profundamente anidados con nombres de clave largos puede requerir de 8 a 12 veces el tamaño bruto en bytes en memoria del heap.
// Validador en streaming O(n) — sin AST, memoria O(1)
function validateJson(input) {
let i = 0;
let depth = 0;
let state = 'VALUE';
while (i < input.length) {
const ch = input[i];
switch (state) {
case 'VALUE':
if (ch === '{' || ch === '[') { depth++; state = ch === '{' ? 'KEY' : 'VALUE'; i++; }
else if (ch === '"') { state = 'STRING_END'; i++; }
else if (ch === 't') { if (input.slice(i, i + 4) === 'true') { i += 4; state = 'SEPARATOR'; } else return false; }
else if (ch === 'f') { if (input.slice(i, i + 5) === 'false') { i += 5; state = 'SEPARATOR'; } else return false; }
else if (ch === 'n') { if (input.slice(i, i + 4) === 'null') { i += 4; state = 'SEPARATOR'; } else return false; }
else if (ch === '-' || (ch >= '0' && ch <= '9')) { state = 'NUMBER'; i++; }
else if (ch === ' ') { i++; }
else return false;
break;
case 'KEY':
if (ch === '"') { state = 'KEY_END'; i++; }
else if (ch === '}') { depth--; state = 'SEPARATOR'; i++; }
else if (ch === ' ') { i++; }
else return false;
break;
case 'KEY_END':
if (ch === '"') { state = 'COLON'; i++; }
else if (ch === '\\') { i += 2; }
else if (ch >= ' ') { i++; }
else return false;
break;
case 'COLON':
if (ch === ':') { state = 'VALUE'; i++; }
else if (ch === ' ') { i++; }
else return false;
break;
case 'STRING_END':
if (ch === '"') { state = 'SEPARATOR'; i++; }
else if (ch === '\\') { i += 2; }
else if (ch >= ' ') { i++; }
else return false;
break;
case 'NUMBER':
if (ch >= '0' && ch <= '9') { i++; }
else if (ch === '.' || ch === 'e' || ch === 'E' || ch === '-' || ch === '+') { i++; }
else if (ch === ' ' || ch === ',' || ch === '}' || ch === ']') { state = ch === ',' ? 'VALUE' : 'SEPARATOR'; }
else return false;
break;
case 'SEPARATOR':
if (ch === ',') { state = 'VALUE'; i++; }
else if (ch === '}' || ch === ']') { depth--; i++; }
else if (ch === ' ') { i++; }
else if (i >= input.length) { /* fin */ }
else return false;
break;
}
}
return depth === 0;
}
Un validador en streaming consigue tiempo O(n) y memoria O(1), lo que lo hace adecuado para validar payloads de múltiples gigabytes sin agotamiento del heap. La contrapartida es la notificación de errores: un validador en streaming no puede informar del contexto estructural preciso de un error (por ejemplo, “se esperaba una coma en la línea 42.372, columna 15”) sin mantener metadatos de posición, lo que empuja la complejidad hacia O(log n) para un rastreador de líneas.
Prevención de Fallos Silenciosos en CI/CD
La clase más peligrosa de fallo de validación JSON ocurre cuando un pipeline CI/CD usa un parser permisivo durante la compilación y un parser estricto en tiempo de ejecución.
Considérese un pipeline de despliegue donde un archivo de configuración TypeScript es procesado por ts-node durante la compilación, que evalúa el archivo como JavaScript, aceptando trailing commas y comillas simples. El artefacto de salida se escribe como un archivo .json. El servicio en producción lee el artefacto usando encoding/json de Go, que es compatible con RFC 8259.
El artefacto pasa la validación de compilación porque ts-node nunca invoca JSON.parse() sobre la salida. Los valores de configuración son correctos. La compilación se completa con éxito. El artefacto se despliega. El fallo se manifiesta solo cuando el servicio de producción intenta revalidar o transformar el artefacto, o cuando un sistema externo lo consume. Para entonces el artefacto ya se ha desplegado y la reversión requiere un ciclo completo de despliegue.
La solución es aplicar la validación RFC 8259 en el punto más temprano posible del pipeline: un hook de pre-commit o un paso de compilación, usando un parser que coincida con el comportamiento del runtime de producción. Cualquier divergencia entre el parseo en tiempo de compilación y en tiempo de ejecución es deuda técnica que eventualmente se materializará como un incidente de producción. El validador fiable más simple es JSON.parse(): está integrado en todos los runtimes modernos, es estrictamente compatible con RFC y se garantiza que coincida con cualquier parser que acompañe a su entorno de producción.