Ключевое слово allOf в JSON Schema и библиотеке Ajv используется для композиции схем через операцию логического «И». Объект считается валидным только в том случае, если он соответствует всем схемам, перечисленным в массиве allOf. Это позволяет строить сложные валидационные правила из небольших переиспользуемых блоков.
Конструкция allOf задаётся как массив схем:
{
"allOf": [
{ "type": "object", "required": ["id"] },
{ "type": "object", "required": ["name"] }
]
}
Объект должен пройти проверку каждой схемы из массива. Если хотя бы одна схема не проходит валидацию, итоговый результат считается ошибочным.
Формально:
В Ajv каждая схема внутри allOf компилируется в отдельную функцию валидации. При запуске проверки:
Особенность Ajv заключается в том, что схемы не просто проверяются последовательно, а предварительно компилируются, что обеспечивает высокую производительность даже при глубокой композиции.
import Ajv from "ajv";
const ajv = new Ajv();
const schema = {
allOf: [
{
type: "object",
properties: {
id: { type: "number" }
},
required: ["id"],
additionalProperties: false
},
{
type: "object",
properties: {
name: { type: "string" }
},
required: ["name"],
additionalProperties: false
}
]
};
const validate = ajv.compile(schema);
console.log(validate({ id: 1, name: "Item" })); // true
console.log(validate({ id: 1 })); // false
Во втором случае объект не проходит проверку второй схемы, так как отсутствует обязательное поле name.
allOf часто используется для декомпозиции схем на логические блоки.
{
"type": "object",
"properties": {
"createdAt": { "type": "string", "format": "date-time" }
},
"required": ["createdAt"]
}
{
"allOf": [
{
"type": "object",
"properties": {
"id": { "type": "number" }
},
"required": ["id"]
},
{
"type": "object",
"properties": {
"createdAt": { "type": "string", "format": "date-time" }
},
"required": ["createdAt"]
}
]
}
Такой подход позволяет формировать модель данных через композицию, избегая дублирования.
В экосистеме JSON Schema allOf часто используется как механизм псевдо-наследования.
{
"type": "object",
"properties": {
"type": { "type": "string" }
},
"required": ["type"]
}
{
"allOf": [
{
"$ref": "#/definitions/base"
},
{
"type": "object",
"properties": {
"type": { "const": "user" },
"email": { "type": "string" }
},
"required": ["email"]
}
]
}
Здесь первая часть задаёт общий контракт, вторая — специализацию.
Ajv активно оптимизирует схемы, содержащие ссылки:
{
"allOf": [
{ "$ref": "#/definitions/a" },
{ "$ref": "#/definitions/b" }
]
}
Во время компиляции:
Это уменьшает накладные расходы при больших схемах.
При использовании allOf ошибки могут приходить из нескольких схем одновременно.
Пример:
const schema = {
allOf: [
{
type: "object",
required: ["id"],
properties: {
id: { type: "number" }
}
},
{
type: "object",
required: ["name"],
properties: {
name: { type: "string" }
}
}
]
};
const validate = ajv.compile(schema);
validate({});
console.log(validate.errors);
Результат содержит:
Ajv группирует ошибки, сохраняя информацию о том, какая именно подсхема их породила. Это важно при отладке сложных схем.
Конструкция может быть вложенной:
{
"allOf": [
{
"allOf": [
{ "type": "object" },
{ "required": ["id"] }
]
},
{
"properties": {
"id": { "type": "number" }
}
}
]
}
В таких случаях проверка разворачивается рекурсивно. Ajv оптимизирует вложенность, объединяя некоторые уровни в единое дерево проверок.
allOf часто используется вместе с:
{
"allOf": [
{
"type": "object"
},
{
"anyOf": [
{ "required": ["email"] },
{ "required": ["phone"] }
]
}
]
}
Логика:
allOf позволяет собирать сложные модели данных из повторно используемых блоков:
{
"allOf": [
{ "$ref": "v1.json" },
{ "$ref": "v2.json" }
]
}
Позволяет расширять контракт без разрушения обратной совместимости.
Комбинирование базового типа и уточняющих правил:
В Ajv allOf обрабатывается эффективно за счёт:
Если первая схема уже заведомо провалилась, последующие проверки могут не выполняться в зависимости от конфигурации strict mode и параметров обработки ошибок.
При использовании allOf важно учитывать взаимодействие с additionalProperties.
{
"allOf": [
{
"properties": { "id": { "type": "number" } }
},
{
"properties": { "name": { "type": "string" } },
"additionalProperties": false
}
]
}
Каждая схема применяет собственные правила. Это может привести к неожиданным конфликтам, если одна схема разрешает дополнительные поля, а другая запрещает.
Если одно и то же поле описано в нескольких схемах с различными типами, результатом станет конфликт валидации.
{
"allOf": [
{ "required": ["id"] },
{ "required": ["id"] }
]
}
Хотя формально корректно, избыточность ухудшает читаемость и сопровождение.
{
"allOf": [
{ "type": "string" },
{ "type": "number" }
]
}
Такая схема всегда будет невалидной, так как типы взаимоисключающие.
allOf выступает как базовый строительный механизм композиции:
В Ajv это один из ключевых инструментов построения масштабируемых систем валидации данных.