Dev Tools

Guía Completa de Validación con JSON Schema: Palabras Clave, Composición y Buenas Prácticas

Introducción: ¿Por Qué Validar Datos con JSON Schema?

En el desarrollo de software moderno, los datos en formato JSON se intercambian constantemente entre servicios, APIs, bases de datos y aplicaciones cliente. Sin embargo, confiar en que los datos recibidos tengan la estructura esperada es un error frecuente que conduce a fallos en producción, vulnerabilidades de seguridad y comportamientos inesperados. JSON Schema proporciona un vocabulario estandarizado para describir y validar la estructura de documentos JSON de forma declarativa.

JSON Schema permite definir reglas precisas sobre qué forma deben tener los datos: qué campos son obligatorios, qué tipos de valores se aceptan, qué rangos numéricos son válidos y qué patrones deben cumplir las cadenas de texto. Funciona como un contrato formal entre el productor y el consumidor de datos. En esta guía cubriremos desde los fundamentos hasta técnicas avanzadas de composición, todo con ejemplos prácticos. Puedes utilizar nuestro validador JSON Schema gratuito para probar los ejemplos mientras sigues esta guía.

¿Qué Es JSON Schema?

JSON Schema es una especificación que permite anotar y validar documentos JSON. Se define a sí mismo como un documento JSON que describe las restricciones que otro documento JSON debe cumplir. El esquema actúa como un plano o plantilla: no contiene datos reales, sino reglas que los datos deben satisfacer para considerarse válidos.

La especificación ha evolucionado a través de varios borradores (drafts). Los más relevantes son:

  • Draft 4 — La primera versión ampliamente adoptada. Introdujo las palabras clave fundamentales como type, properties, required y additionalProperties.
  • Draft 6 — Añadió const, contains, propertyNames y el booleano examples.
  • Draft 7 — La versión más utilizada actualmente. Incorporó if/then/else para validación condicional, readOnly/writeOnly, y los formatos date, time y idn-email.
  • Draft 2019-09 y 2020-12 — Versiones más recientes con vocabularios modulares, $dynamicRef y prefixItems, pero con menor adopción en herramientas.

Esta guía se centra en el Draft 7, ya que cuenta con el soporte más amplio en bibliotecas de validación como Ajv (JavaScript), jsonschema (Python), everit-org (Java) y muchas más.

Estructura Básica de un Esquema

Todo esquema JSON comienza con una declaración mínima que identifica la versión del borrador y describe el propósito del esquema:

{"$schema": "http://json-schema.org/draft-07/schema#", "title": "Producto", "description": "Esquema para un producto en el catálogo", "type": "object"}

La propiedad $schema indica al validador qué versión de la especificación debe aplicar. Las propiedades title y description son metadatos informativos que no afectan la validación pero mejoran la documentación.

Palabras Clave Fundamentales del Draft 7

Las palabras clave (keywords) son el corazón de JSON Schema. Cada una define una restricción específica que los datos deben cumplir. A continuación exploramos las más importantes agrupadas por función.

type — Definir el Tipo de Dato

La palabra clave type restringe el tipo de valor permitido. Los tipos válidos en JSON Schema son: string, number, integer, boolean, object, array y null. También se puede especificar un arreglo de tipos para aceptar múltiples posibilidades: {"type": ["string", "null"]} acepta una cadena o un valor nulo.

Es fundamental elegir el tipo correcto desde el principio. Por ejemplo, un campo de precio debería ser number (no string) para permitir validaciones numéricas posteriores como minimum y maximum. Un identificador que podría contener ceros a la izquierda (como un código postal) debería ser string aunque parezca numérico.

required — Campos Obligatorios

La palabra clave required se aplica a objetos y especifica una lista de nombres de propiedades que deben estar presentes en el documento. Se define como un arreglo de cadenas a nivel del objeto, no dentro de cada propiedad individual:

{"type": "object", "required": ["nombre", "email", "edad"]}

Si alguna de las propiedades listadas en required está ausente en los datos, la validación falla. Es importante distinguir entre un campo obligatorio que está presente con valor null (válido si el tipo lo permite) y un campo que simplemente no existe en el documento (inválido si está en required).

properties — Definir Propiedades de Objetos

La palabra clave properties define los esquemas individuales para cada propiedad de un objeto. Cada clave dentro de properties corresponde a un nombre de propiedad, y su valor es un subesquema que describe las restricciones de esa propiedad:

{"type": "object", "properties": {"nombre": {"type": "string", "minLength": 1, "maxLength": 100}, "edad": {"type": "integer", "minimum": 0, "maximum": 150}, "email": {"type": "string", "format": "email"}}}

Las propiedades definidas aquí no son automáticamente obligatorias; para hacerlas obligatorias, deben listarse en required. Las propiedades no listadas en properties son permitidas por defecto, a menos que se restrinja con additionalProperties.

additionalProperties — Controlar Propiedades Extra

Cuando se establece "additionalProperties": false, el objeto no puede contener ninguna propiedad que no esté definida en properties o patternProperties. Esto es extremadamente útil para APIs donde se desea rechazar datos con campos desconocidos que podrían indicar un error del cliente o un intento de inyección. También se puede establecer como un esquema para validar el tipo de las propiedades adicionales: "additionalProperties": {"type": "string"} permite propiedades extra siempre que sus valores sean cadenas.

items — Validar Elementos de Arreglos

La palabra clave items define el esquema que cada elemento de un arreglo debe cumplir. Cuando se proporciona un solo esquema, todos los elementos deben ser válidos contra ese esquema:

{"type": "array", "items": {"type": "string", "minLength": 1}, "minItems": 1, "maxItems": 50, "uniqueItems": true}

Este ejemplo define un arreglo de cadenas no vacías, con al menos un elemento, máximo 50, y sin duplicados. La combinación de items con minItems, maxItems y uniqueItems permite un control granular sobre las colecciones de datos.

En Draft 7 también se puede usar items como un arreglo de esquemas para validar tuplas (arreglos de longitud fija donde cada posición tiene un tipo específico), junto con additionalItems para controlar elementos adicionales más allá de las posiciones definidas.

enum — Valores Permitidos

La palabra clave enum restringe un valor a una lista cerrada de opciones permitidas. Cada valor en el arreglo enum debe ser único y puede ser de cualquier tipo JSON:

{"type": "string", "enum": ["activo", "inactivo", "pendiente", "suspendido"]}

Los enumerados son ideales para campos de estado, categorías, roles de usuario y cualquier campo con un conjunto finito de valores válidos. A diferencia de const (que restringe a un único valor), enum permite múltiples opciones. La validación es estricta: la comparación es por igualdad exacta, por lo que "Activo" fallaría si el enum espera "activo".

pattern — Validación con Expresiones Regulares

La palabra clave pattern valida que una cadena coincida con una expresión regular ECMA-262. Esto es extremadamente útil para validar formatos específicos que no están cubiertos por las palabras clave de formato integradas:

{"type": "string", "pattern": "^[A-Z]{2}-[0-9]{4}$"}

Este patrón valida códigos con formato como AB-1234: dos letras mayúsculas, un guión y cuatro dígitos. Los patrones más comunes incluyen validación de códigos postales, números de teléfono, identificadores internos y formatos de fecha personalizados. Es recomendable anclar los patrones con ^ y $ para evitar coincidencias parciales inesperadas.

Validaciones Numéricas y de Cadenas

Además de las palabras clave principales, JSON Schema ofrece restricciones específicas para tipos numéricos y de cadena que permiten un control fino sobre los valores aceptados.

Restricciones Numéricas

Para valores de tipo number o integer, se dispone de: minimum y maximum (valores inclusivos), exclusiveMinimum y exclusiveMaximum (valores exclusivos), y multipleOf (el valor debe ser múltiplo del número especificado). Por ejemplo, para un campo de porcentaje: {"type": "number", "minimum": 0, "maximum": 100, "multipleOf": 0.01} acepta valores entre 0 y 100 con hasta dos decimales.

Restricciones de Cadenas

Para valores de tipo string: minLength y maxLength controlan la longitud, pattern valida contra una expresión regular, y format proporciona validaciones semánticas predefinidas como email, uri, date, date-time, ipv4, ipv6, hostname y uuid. Los formatos son opcionales en validación por defecto en muchas bibliotecas; hay que activarlos explícitamente en el validador.

Composición de Esquemas: allOf, anyOf, oneOf y not

Las palabras clave de composición permiten combinar múltiples esquemas de formas poderosas y flexibles. Son esenciales para modelar estructuras de datos complejas sin duplicar definiciones.

allOf — Cumplir Todos los Esquemas

El dato debe ser válido contra todos los subesquemas listados. Se utiliza comúnmente para extender o componer esquemas base. Por ejemplo, para definir un empleado que debe cumplir tanto el esquema de persona como el esquema de datos laborales:

{"allOf": [{"$ref": "#/definitions/persona"}, {"$ref": "#/definitions/datosLaborales"}]}

Esto es equivalente a la herencia en programación orientada a objetos: el empleado hereda todas las restricciones de persona y además añade las de datos laborales. Es importante tener cuidado con conflictos entre esquemas combinados con allOf; si un esquema exige "type": "string" y otro "type": "number", ningún dato será válido.

anyOf — Cumplir Al Menos Un Esquema

El dato debe ser válido contra al menos uno de los subesquemas. Es útil para campos polimórficos que pueden aceptar diferentes formatos:

{"anyOf": [{"type": "string", "format": "email"}, {"type": "string", "pattern": "^\\+[0-9]{1,3}-[0-9]{4,14}$"}]}

Este esquema acepta un email o un número de teléfono internacional. Con anyOf, si el dato coincide con múltiples esquemas, sigue siendo válido.

oneOf — Cumplir Exactamente Un Esquema

Similar a anyOf, pero el dato debe ser válido contra exactamente uno de los subesquemas, ni más ni menos. Esto es útil cuando los formatos son mutuamente excluyentes y se necesita garantizar que no haya ambigüedad:

{"oneOf": [{"type": "object", "properties": {"tipo": {"const": "tarjeta"}, "numero": {"type": "string"}}, "required": ["tipo", "numero"]}, {"type": "object", "properties": {"tipo": {"const": "transferencia"}, "iban": {"type": "string"}}, "required": ["tipo", "iban"]}]}

Este ejemplo modela un método de pago que puede ser tarjeta o transferencia, pero no ambos. Si un documento coincide con más de un subesquema, la validación falla.

not — Negación

El dato no debe ser válido contra el esquema proporcionado. Se usa para excluir valores o patrones específicos:

{"not": {"type": "null"}}

Este esquema rechaza valores nulos. La palabra clave not es particularmente útil en combinación con otras palabras de composición para crear restricciones sofisticadas como "acepta cualquier cadena excepto las que coincidan con este patrón".

Referencias con $ref: Reutilización de Esquemas

La palabra clave $ref es fundamental para mantener esquemas organizados y evitar la duplicación. Permite referenciar otras definiciones dentro del mismo documento o en documentos externos mediante punteros JSON (JSON Pointer).

Definiciones Locales

La convención más común es definir subesquemas reutilizables en una sección definitions (Draft 7) o $defs (Draft 2019-09+) en la raíz del esquema:

{"definitions": {"direccion": {"type": "object", "properties": {"calle": {"type": "string"}, "ciudad": {"type": "string"}, "codigoPostal": {"type": "string", "pattern": "^[0-9]{5}$"}}, "required": ["calle", "ciudad"]}}, "type": "object", "properties": {"direccionEnvio": {"$ref": "#/definitions/direccion"}, "direccionFacturacion": {"$ref": "#/definitions/direccion"}}}

En este ejemplo, el esquema de dirección se define una sola vez y se referencia dos veces. Si se necesita modificar la estructura de una dirección, solo hay que cambiarla en un lugar. El puntero #/definitions/direccion indica: empezar en la raíz del documento (#), navegar a la propiedad definitions, y luego a la propiedad direccion.

Referencias Externas

También es posible referenciar esquemas en archivos externos: {"$ref": "esquemas/direccion.json"} o con URLs completas: {"$ref": "https://example.com/schemas/direccion.json#/definitions/pais"}. Las referencias externas son esenciales en arquitecturas de microservicios donde múltiples servicios comparten definiciones de datos comunes.

Validación Condicional: if / then / else

Introducida en Draft 7, la validación condicional permite aplicar diferentes restricciones según el valor de ciertos campos. Esto reemplaza patrones complejos que antes requerían combinaciones de oneOf con esquemas duplicados:

{"type": "object", "properties": {"pais": {"type": "string"}, "codigoPostal": {"type": "string"}}, "if": {"properties": {"pais": {"const": "ES"}}}, "then": {"properties": {"codigoPostal": {"pattern": "^[0-9]{5}$"}}}, "else": {"properties": {"codigoPostal": {"pattern": "^[A-Z0-9 -]{3,10}$"}}}}

Este esquema valida que si el país es España, el código postal tenga exactamente 5 dígitos; de lo contrario, acepta un formato más flexible. La combinación de if/then/else hace que los esquemas condicionales sean mucho más legibles que las alternativas basadas en composición.

Casos de Uso Reales de JSON Schema

JSON Schema no es solo una herramienta teórica; tiene aplicaciones prácticas en múltiples etapas del ciclo de desarrollo de software.

Validación de APIs REST

Uno de los usos más comunes es validar el cuerpo de las peticiones HTTP en APIs REST. Frameworks como Express (con middleware como ajv), FastAPI (Python), y Spring Boot (Java) pueden usar esquemas JSON para rechazar automáticamente peticiones con datos malformados antes de que lleguen a la lógica de negocio. Esto reduce el código de validación manual, centraliza las reglas y proporciona mensajes de error consistentes al cliente.

Validación de Configuración

Los archivos de configuración como package.json, tsconfig.json, .eslintrc.json y docker-compose.yml (convertido a JSON) se validan contra esquemas JSON. El proyecto SchemaStore.org recopila cientos de esquemas para herramientas populares, y editores como VS Code los utilizan para proporcionar autocompletado y validación en tiempo real mientras se editan archivos de configuración.

Documentación de APIs con OpenAPI

La especificación OpenAPI (anteriormente Swagger) utiliza JSON Schema como base para describir los modelos de datos de una API. Los esquemas definidos en un documento OpenAPI siguen la sintaxis de JSON Schema con algunas extensiones. Esto permite generar documentación interactiva, clientes SDK y tests automáticos a partir de un único archivo de especificación.

Validación en Formularios

Bibliotecas como react-jsonschema-form, vue-jsonschema-form y Angular JSON Schema Form generan formularios HTML automáticamente a partir de esquemas JSON. El esquema define los campos, sus tipos, las validaciones y las dependencias entre campos. Esto permite crear formularios dinámicos sin escribir código de interfaz manualmente, ideal para aplicaciones de administración y CMS.

Pruebas y Generación de Datos

Herramientas como json-schema-faker y Hypothesis (Python) generan datos de prueba aleatorios que cumplen con un esquema dado. Esto es invaluable para pruebas de carga, pruebas de propiedades (property-based testing) y para poblar entornos de desarrollo con datos realistas que respetan todas las restricciones definidas.

Migración y Evolución de Datos

Cuando los esquemas cambian con el tiempo (se agregan campos, se modifican tipos, se eliminan propiedades), JSON Schema permite implementar estrategias de versionado. Se pueden mantener múltiples versiones de un esquema y validar documentos contra la versión correspondiente, facilitando migraciones graduales sin romper la compatibilidad.

Buenas Prácticas para Esquemas JSON Schema

Después de años de uso en producción, la comunidad ha identificado prácticas que mejoran la mantenibilidad, claridad y robustez de los esquemas.

1. Usar Descripciones en Todas las Propiedades

Cada propiedad debería tener una description que explique su propósito, valores esperados y cualquier particularidad. Esto convierte al esquema en documentación viva que siempre está sincronizada con los datos reales.

2. Preferir Esquemas Estrictos

Utilizar "additionalProperties": false siempre que sea práctico. Los esquemas permisivos aceptan datos inesperados silenciosamente, lo que dificulta la detección de errores. Un esquema estricto falla rápidamente cuando llegan datos malformados, haciendo más fácil identificar y corregir problemas.

3. Modularizar con $ref

Extraer subesquemas comunes a definitions y referenciarlos con $ref. Esto reduce la duplicación, facilita el mantenimiento y permite que los cambios se propaguen automáticamente a todos los puntos que usan esa definición.

4. Incluir Ejemplos

La palabra clave examples permite añadir valores de ejemplo que ilustran datos válidos. Los ejemplos ayudan a los consumidores del esquema a entender rápidamente qué forma deben tener los datos sin necesidad de interpretar las restricciones abstractas.

5. Validar en Múltiples Capas

No confiar en una sola capa de validación. Validar en el cliente para retroalimentación rápida al usuario, validar en la API para seguridad y consistencia, y validar en la capa de persistencia para integridad de datos. El mismo esquema JSON puede usarse en las tres capas.

6. Evolucionar Esquemas con Cuidado

Los cambios que rompen la compatibilidad (eliminar campos obligatorios, cambiar tipos, restringir enumerados) deben manejarse con versionado. Los cambios aditivos (nuevos campos opcionales, ampliar enumerados) son generalmente seguros. Documentar cada cambio y proporcionar guías de migración cuando sea necesario.

7. Probar los Esquemas

Escribir tests unitarios para los esquemas: verificar que los datos válidos pasan, que los datos inválidos fallan con los errores esperados y que los casos límite se manejan correctamente. Las herramientas de prueba de esquemas permiten definir conjuntos de datos de prueba positivos y negativos para cada esquema.

Conclusión: JSON Schema como Contrato de Datos

JSON Schema transforma la validación de datos de una tarea ad hoc distribuida en el código a un contrato formal, declarativo y reutilizable. Al definir esquemas claros y completos, los equipos de desarrollo establecen expectativas explícitas sobre la forma de los datos, reducen errores en producción, mejoran la documentación y habilitan la generación automática de herramientas como formularios, documentación y datos de prueba.

Ya sea que estés construyendo una API REST, configurando un pipeline de datos o definiendo formularios dinámicos, JSON Schema proporciona la base para garantizar la calidad y consistencia de tus datos. Te recomendamos empezar con esquemas simples e ir añadiendo complejidad gradualmente, utilizando nuestro validador JSON Schema en línea para verificar tus esquemas y datos durante el proceso de aprendizaje.

← Volver al Blog