Formateo, validación y esquema JSON en la práctica
Resumen (TL;DR)
Un equipo con el que trabajé el mes pasado desplegó una edición de una sola línea a feature_flags.json. JSON.parse la aceptó, CI estaba en verde y, en cuanto llegó a staging, toda rama de checkout volvió al camino de código legacy porque flags.checkout_v2 era la cadena "true" en lugar del booleano true. “Revisar JSON” se había convertido silenciosamente en tres actividades distintas, y solo habían realizado las dos primeras. El formateo bonito reforma el espaciado para que un humano pueda leer un blob; no verifica nada. La validación sintáctica confirma que una cadena se puede parsear como JSON según RFC 8259 —corchetes emparejados, comillas correctas, literales numéricos válidos— y eso es precisamente lo que hace JSON.parse. La validación estructural es un paso aparte que pregunta si el valor parseado tiene la forma que tu programa espera: campos requeridos, tipos correctos, valores enum permitidos, longitudes de cadena, rangos numéricos. Ese último paso es exactamente para lo que se diseñó JSON Schema, y es el que la mayoría de los equipos omiten hasta que un bug de producción como el anterior los obliga a añadirlo. Una pipeline fiable usa los tres en la capa correcta: formatea cuando necesites leer los datos, parsea para atrapar entrada malformada, y corre una comprobación de esquema estilo Ajv en los límites de confianza: peticiones API entrantes, archivos de configuración y mensajes entre servicios.
Antecedentes y conceptos
JSON está definido por RFC 8259 (y el equivalente ECMA-404). La gramática es pequeña a propósito. Un documento JSON es uno de: una cadena, un número, true, false, null, un array o un objeto. Las cadenas van entre comillas dobles y soportan una lista corta de escapes incluyendo \n, \t, \", \\ y \uXXXX para unidades de código Unicode. Los números siguen una gramática decimal con signo opcional, parte fraccional y exponente, pero no hay distinción entre entero y punto flotante a nivel sintáctico. Los objetos son colecciones desordenadas de miembros con clave-cadena, los arrays son listas ordenadas, y el espaciado fuera de las cadenas es insignificante.
Lo que JSON intencionalmente no incluye es casi igual de importante. No hay comentarios, ni comas finales, ni cadenas con comillas simples, ni literales hex ni binarios. Los caracteres Unicode fuera del rango ASCII deben aparecer como bytes UTF-8 crudos en el stream codificado o como escapes \u. Extensiones como JSON5 y HJSON relajan algunas de estas reglas, pero son formatos separados y los parsers que aceptan JSON estricto los rechazarán.
Una vez parseado un documento, su validez sintáctica no dice nada sobre si es el dato correcto. Un payload de login como {"user": "x", "pass": "y"} es JSON perfectamente válido, aunque tu endpoint esperara {"username": "...", "password": "..."}. Para atrapar esa clase de error necesitas un esquema: una descripción legible por máquina de qué cuenta como documento aceptable. JSON Schema (el meta-esquema recomendado actual es Draft 2020-12) llena ese hueco. Soporta campos requeridos, restricciones de tipo, enum y const, patrones de cadena vía regex, rangos numéricos, items y unicidad de array, properties y additionalProperties de objeto, y composición vía allOf, oneOf, anyOf y $ref. Ajv 8.12 compila un esquema una vez a una función validadora de JavaScript, lo suficientemente rápida para rutas calientes; en un servicio Node 20.11 que instrumenté, una comprobación Ajv compilada sobre un cuerpo de petición típico se situaba en el rango de decenas de microsegundos.
El formateo bonito es el más mundano de los tres. Solo inserta espaciado —indentación y saltos de línea— sin cambiar el significado. La mayoría de editores y herramientas de línea de comandos lo hacen; las devtools del navegador lo hacen automáticamente en el panel de red. Es útil para humanos e irrelevante para máquinas.
Comparación y datos
| Aspecto | Formateo bonito | Validación sintáctica | Validación con JSON Schema |
|---|---|---|---|
| Propósito | Hacer los datos legibles | Asegurar que el texto se parsea como JSON | Asegurar que los datos parseados coinciden con una forma esperada |
| Detecta | Nada, solo espaciado | Corchetes no emparejados, comillas malas, escapes inválidos, comas finales | Campos faltantes, tipos erróneos, valores fuera de rango, claves desconocidas |
| No detecta | Problemas estructurales, semánticos | Problemas estructurales (forma esperada), reglas de negocio | Reglas semánticas más allá del esquema, invariantes entre campos sin keywords custom |
| Herramientas típicas | JSON.stringify(obj, null, 2), jq, formateador del IDE | JSON.parse, jq -e, cualquier parser | Ajv 8.x, python-jsonschema, validadores OpenAPI |
| Dónde ejecutarlo | Herramientas de desarrollo, logs | En cada límite de parseo (implícito) | En petición/respuesta API, carga de config, límites de mensajes |
Las tres columnas no son alternativas; son capas secuenciales. Abrir un package-lock.json de 12 MB en una sola línea en un editor crudo es cómo terminas con una cadena que tu herramienta de diff se niega a comparar, y formatearla en forma indentada no valida nada: solo hace legible la forma. Esa distinción importa porque las dos capas siguientes, sintaxis y esquema, a menudo se confunden con “lo formateé y no explotó”. Formatea para leer, parsea para atrapar texto malformado, y valida con un esquema para atrapar forma errónea. Desplegar solo los dos primeros es un hueco común.
Escenarios reales
Escenario 1 — Depurar una respuesta API. Una pasarela de pagos de terceros devuelve un cuerpo JSON de 600 líneas y el navegador lo muestra como una sola línea. Formatearlo —ya sea con devtools del navegador, un formateador local o curl ... | jq .— lo convierte en algo que un humano puede hojear. Aquí no ocurre ninguna validación; el objetivo es legibilidad mientras buscas el campo que se ve mal, y la disciplina importante es no llamar a este paso “validado”.
Escenario 2 — Cargar un archivo de configuración. Un servicio lee config.json al arrancar. Un parser JSON estricto atrapa errores sintácticos como una coma final perdida y se niega a arrancar, que es el comportamiento correcto. Pero, como mostró el incidente inicial, un archivo válido con retries: "three" en lugar de retries: 3 parseará bien y solo fallará cuando el código intente comparar una cadena con un número. El patrón que me ha funcionado es poner Ajv.compile(schema)(config) en las primerísimas líneas del punto de entrada y llamar a process.exit(1) en caso de fallo: un cambio de cinco minutos que se ha pagado solo las dos veces siguientes que alguien editó el archivo a mano.
Escenario 3 — Verificar un contrato OpenAPI. Un equipo despliega un documento OpenAPI 3.1 que describe endpoints, cuerpos de petición y formas de respuesta bajo components.schemas. Los tests de contrato toman los payloads de ejemplo de la especificación y los validan contra los esquemas usando un validador JSON Schema. Cuando una implementación de servidor deriva —por ejemplo, empieza a devolver un entero donde la especificación prometía una cadena— el test de contrato marca la discrepancia antes de que un cliente se rompa en producción. Es el mismo motor JSON Schema que usarías para un archivo de config suelto; la diferencia es la escala de cobertura.
Errores comunes
“JSON.parse basta para validar JSON.” Valida la sintaxis, no la forma. Un parser devolverá alegremente un objeto al que le falta la mitad de los campos que tu código necesita. Trata JSON.parse como una puerta contra texto malformado y añade una comprobación de esquema encima cuando los datos crucen un límite de confianza.
“JSON Schema es solo de servidor.” Validar en el navegador antes de enviar una petición da retroalimentación instantánea al usuario y reduce la carga del servidor. Muchas librerías de formularios y el propio Ajv corren cómodamente en el navegador. La validación en servidor aún debe ejecutarse —nunca confíes en el cliente— pero los chequeos en cliente mejoran la UX sin debilitar el modelo de seguridad.
“JSON5 es solo JSON con comentarios.” JSON5 añade comentarios, claves sin comillas, comas finales, números hex y más. Eso lo hace más amigable como formato de config editado por humanos —el consumidor más conocido es tsconfig.json, que en realidad usa JSONC (un superset no estándar diferente)— pero cualquier cosa que siga estrictamente RFC 8259 no lo aceptará. Usa JSON5/JSONC donde el consumidor documente soporte; emite JSON estricto al escribir a la red o a cualquier herramienta cuyo parser no controles.
“YAML es solo JSON con indentación.” Todo documento JSON válido es YAML válido, pero YAML añade características —anclas y alias, tipos etiquetados, múltiples documentos en un archivo, escalares de bloque y plegados, parseo sensible a indentación— que introducen bugs que JSON no tiene. El clásico es el llamado “problema de Noruega”: YAML 1.1 interpreta NO como el booleano false a menos que lo entrecomilles. Pasar de JSON a YAML “para hacerlo legible” cambia una clase de problemas por otra.
Lista de verificación
- ¿Solo necesitas leer los datos? Formatéalos. No afirmes que están “validados”.
- ¿Están cruzando un límite de confianza (petición HTTP, cola de mensajes, archivo de configuración)? Parsea y luego corre una comprobación de JSON Schema, no solo un parseo.
- ¿Te importan los campos desconocidos? Establece
additionalProperties: falseen el esquema y decide si rechazar o eliminar extras. - ¿Son los errores accionables? Configura el validador (por ejemplo, Ajv con
allErrors: true) para devolver cada violación y que los usuarios vean todos los problemas de una vez. - ¿Vive el esquema junto al código que usa los datos? La deriva entre una especificación y una implementación es más fácil de prevenir cuando el esquema es fuente de verdad para ambos.
- ¿Es el formato lo suficientemente estable para JSON? Si los humanos necesitan editarlo y controlas el parser, JSON5/JSONC o TOML pueden ser más tolerantes. Si las máquinas lo intercambian, quédate con JSON estricto.
Herramienta relacionada
El formateador JSON de Patrache Studio corre en el navegador, así que el payload que pegues nunca sale de tu máquina; útil cuando inspeccionas una respuesta de producción que contiene datos personales. Los payloads rara vez viven solos: si el JSON que formateas incluye un binario embebido, las reglas en Base64 y codificación URL: propósito, trampas, uso correcto explican por qué el blob se expande y cuándo es apropiado un transporte diferente. Si el payload incluye IDs, UUID v1 vs v4 vs v7: elegir una clave primaria de BD cubre por qué la versión exacta que generas en el servidor afecta cómo los sistemas posteriores indexan, ordenan y cachean ese JSON.
Referencias
- IETF RFC 8259, “The JavaScript Object Notation (JSON) Data Interchange Format” — https://datatracker.ietf.org/doc/html/rfc8259
- JSON Schema Specification (Draft 2020-12) — https://json-schema.org/specification
- Ajv — Another JSON Schema Validator — https://ajv.js.org/
- MDN, “Working with JSON” — https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON