29/12/2011
En el mundo del diseño de APIs y la validación de datos, JSON Schema se ha convertido en una herramienta fundamental. Dentro de su especificación, encontramos palabras clave para la composición de esquemas que nos permiten crear estructuras complejas y modulares. Una de las más poderosas, y a menudo malinterpretada, es allOf. Su propósito principal es la reutilización de esquemas, permitiendo mantener una única fuente de verdad y facilitando enormemente el mantenimiento. Sin embargo, un uso incorrecto puede llevar a esquemas ilógicos e inconsistentes que son imposibles de validar.

Este artículo es una guía profunda sobre cuándo, cómo y por qué usar `allOf`. Exploraremos su funcionamiento, los casos de uso válidos que te ahorrarán tiempo y esfuerzo, y los patrones de error más comunes que debes evitar a toda costa para garantizar que tus esquemas sean robustos, lógicos y funcionales.
¿Qué es `allOf` y Cómo se Evalúa?
En esencia, `allOf` es un constructor que actúa como un AND lógico. Para que una instancia JSON sea válida frente a un esquema que utiliza `allOf`, debe ser válida frente a todos y cada uno de los sub-esquemas listados en su array. Es una forma de aplicar múltiples conjuntos de reglas a un mismo dato.
Sintaxis Básica
La palabra clave `allOf` siempre espera un array de esquemas. Cada elemento de este array es un esquema independiente que se aplicará al dato a validar.
Veamos un ejemplo simple en YAML, que es común en definiciones de OpenAPI:
allOf: - title: time type: object properties: time: type: string - title: date type: object properties: date: type: string
Y su equivalente en JSON:
{ "allOf": [ { "title": "time", "type": "object", "properties": { "time": { "type": "string" } } }, { "title": "date", "type": "object", "properties": { "date": { "type": "string" } } } ] } El Proceso de Evaluación: Un AND Estricto
Imaginemos que tenemos los dos sub-esquemas del ejemplo anterior, `$time` y `$date`. La lógica de validación se convierte en: $time && $date. Un objeto JSON debe cumplir con ambos para ser válido.
Por lo tanto, el siguiente JSON sería válido, ya que cumple con las propiedades definidas en ambos sub-esquemas:
{ "time": "08:15:00+06:00", "date": "2022-01-22" } Aquí es donde surge una confusión común. ¿Qué pasaría con este otro JSON?
{ "date": "2022-01-22" } Podríamos pensar que no es válido porque le falta la propiedad `time`. Sin embargo, ¡sí es válido! La razón es que ninguno de los sub-esquemas originales especificó que sus propiedades eran required. El primer sub-esquema (`$time`) solo dice que si la propiedad `time` existe, debe ser un string. Como no existe, no incumple la regla. Lo mismo ocurre con el segundo sub-esquema. Por lo tanto, el objeto es válido frente a ambos.
De hecho, incluso un objeto completamente ajeno como el siguiente también sería válido, por la misma razón:
{ "temperatura": 25, "unidad": "C" } Para que la validación funcione como se espera, necesitaríamos añadir la palabra clave `required` a nuestros sub-esquemas. Este es un detalle crucial para entender el comportamiento de `allOf`.
Casos de Uso Válidos y Recomendados
A pesar de sus sutilezas, `allOf` es increíblemente útil cuando se aplica correctamente. Su principal fortaleza reside en la composición de esquemas.
1. Reutilización de Esquemas Base
Este es el caso de uso por excelencia. Imagina que tienes un recurso `User` con muchas propiedades. A menudo, en otras partes de tu API, solo necesitas un subconjunto de esa información (por ejemplo, para listar el autor de un post). En lugar de duplicar propiedades, puedes crear un esquema base y componerlo.
Primero, definimos un esquema base `UserExcerpt` con los campos esenciales:
# components/schemas/UserExcerpt.yaml title: UserExcerpt required: - id - email type: object properties: id: type: string name: type: string email: type: string avatar: type: string
Ahora, podemos definir nuestro esquema completo `User` reutilizando `UserExcerpt` y añadiendo las propiedades adicionales:
# components/schemas/User.yaml title: User allOf: - $ref: '#/components/schemas/UserExcerpt' - type: object properties: phone: type: string dob: type: string createdAt: type: string
De esta forma, si alguna vez necesitas cambiar el formato del `id` o del `email`, solo lo haces en `UserExcerpt` y el cambio se propagará a todos los esquemas que lo utilicen. Esto es mantener una única fuente de verdad.
2. Añadir Metadatos a Referencias (`$ref`) en OpenAPI 3.0
OpenAPI 3.1 permite añadir propiedades informativas como `description` o `summary` como "hermanos" de una referencia `$ref`. Sin embargo, en OpenAPI 3.0 esto no era válido. `allOf` se convirtió en una solución elegante para este problema.

Si querías reutilizar un esquema `ResourceId` pero darle una descripción específica en el contexto de una transacción, en OpenAPI 3.0 lo harías así:
properties: transactionId: description: ID único de la transacción. allOf: - $ref: '#/components/schemas/ResourceId'
Esto funciona porque la `description` no es una palabra clave de validación y no entra en conflicto con el esquema referenciado. Es una forma de enriquecer el contexto sin romper las reglas de la especificación.
¡Peligro! Errores Comunes y Esquemas Ilógicos
El mayor error es pensar en `allOf` como "herencia" o "extensión" en el sentido de la programación orientada a objetos. Palabras como "sobrescribir" o "extender" deberían ser una señal de alerta. Un objeto debe ser válido contra todos los sub-esquemas de forma independiente.
1. Conflicto de Tipos
Este es el error más obvio. Un valor no puede ser simultáneamente un string y un objeto. El siguiente esquema es lógicamente imposible y nunca validará nada.
allOf: - type: string - type: object
Este problema puede ser más sutil cuando se usan referencias. Si tienes un `$ref` que apunta a un esquema de tipo `string` e intentas combinarlo con `type: integer`, el resultado será un esquema que nada puede satisfacer.
2. Conflicto en Esquemas "Cerrados"
Un esquema "cerrado" es aquel que no permite propiedades adicionales, lo cual se define con `additionalProperties: false`.
Considera el siguiente `allOf`:
allOf: - type: object properties: date: type: string additionalProperties: false - type: object properties: time: type: string additionalProperties: false
Veamos qué sucede al validar este JSON: `{ "date": "2022-01-22" }`.
- Es válido contra el primer sub-esquema (contiene `date` y no hay propiedades adicionales).
- No es válido contra el segundo sub-esquema. ¿Por qué? Porque el segundo esquema solo conoce la propiedad `time`. Desde su perspectiva, `date` es una propiedad adicional, y `additionalProperties: false` lo prohíbe.
Para que esta composición funcione, las propiedades de ambos sub-esquemas deberían estar declaradas en ambos, o bien permitir propiedades adicionales.
Tabla Comparativa: `allOf`, `anyOf`, `oneOf`
Para poner `allOf` en contexto, es útil compararlo con otros operadores de composición de JSON Schema.
| Característica | allOf (Y Lógico) | anyOf (O Lógico) | oneOf (O Exclusivo) |
|---|---|---|---|
| Condición de Validez | Debe ser válido contra TODOS los sub-esquemas. | Debe ser válido contra UNO O MÁS de los sub-esquemas. | Debe ser válido contra EXACTAMENTE UNO de los sub-esquemas. |
| Uso Común | Combinar fragmentos de esquemas, añadir restricciones a un modelo base. | Permitir múltiples tipos o estructuras para un mismo campo (ej: un ID puede ser string o número). | Elegir una única estructura de un conjunto de opciones mutuamente excluyentes (ej: una respuesta es un objeto de éxito o un objeto de error). |
| Riesgo Principal | Fácil de crear esquemas ilógicos si hay conflictos entre sub-esquemas. | Puede ser demasiado permisivo si los esquemas se superponen. | Un dato podría validar contra más de un sub-esquema, lo que provocaría un fallo de validación `oneOf`. |
Preguntas Frecuentes (FAQ)
- ¿Puedo usar `allOf` para "extender" un esquema como en la programación orientada a objetos?
- No. Este es el error conceptual más común. `allOf` no es herencia. No puedes "sobrescribir" una propiedad de un esquema base en otro. La instancia final debe ser válida contra ambos esquemas de forma independiente. Piénsalo como aplicar múltiples interfaces, no como extender una clase.
- ¿Qué pasa si un objeto vacío `{}` se valida con mi esquema `allOf`?
- Esto casi siempre indica que tus sub-esquemas no están usando la palabra clave `required`. `allOf` combina las reglas, pero si las reglas son débiles (por ejemplo, solo definen tipos de propiedades opcionales), el resultado también será débil.
- ¿Es `allOf` la única forma de reutilizar esquemas?
- No. La principal herramienta de reutilización es `$ref`, que permite incrustar un esquema en otro. `allOf` es una herramienta de composición que a menudo se usa junto con `$ref` para combinar un esquema referenciado con restricciones adicionales.
- ¿Cuándo debería preferir `anyOf` o `oneOf` sobre `allOf`?
- Usa `allOf` cuando necesites que un objeto cumpla con múltiples conjuntos de criterios simultáneamente (ej: debe ser un `UserExcerpt` Y tener propiedades de contacto). Usa `anyOf` o `oneOf` cuando un campo puede adoptar diferentes formas mutuamente excluyentes (ej: un pago puede ser con `creditCardDetails` o con `paypalDetails`, pero no ambos).
Conclusión: Usa `allOf` con Precisión
La palabra clave `allOf` es una herramienta de precisión en tu arsenal de JSON Schema. Su poder reside en la capacidad de crear esquemas complejos y mantenibles a través de la composición y la reutilización. La regla de oro es simple: piensa siempre en `allOf` como un AND lógico. Cada pieza del JSON debe satisfacer todas y cada una de las condiciones impuestas por los sub-esquemas.
Evita la tentación de verlo como un mecanismo de herencia y presta especial atención a los posibles conflictos de tipos y a las restricciones como `additionalProperties: false`. Al hacerlo, aprovecharás todo su potencial para construir APIs mejor diseñadas, más robustas y más fáciles de mantener a largo plazo.
Si quieres conocer otros artículos parecidos a Dominando allOf en JSON Schema: Guía Completa puedes visitar la categoría Juegos.
