Динамические требования валидации возникают тогда, когда структура или ограничения данных зависят от значений других полей, контекста выполнения или внешних условий. В контексте Ajv это один из ключевых сценариев использования, поскольку библиотека позволяет не только проверять статическую структуру JSON, но и строить поведение схемы на основе входных данных.
Одним из базовых механизмов реализации динамических требований является конструкция условной валидации:
Эта конструкция позволяет описывать зависимые правила без необходимости писать пользовательский код.
const schema = {
type: "object",
properties: {
type: { enum: ["user", "admin"] },
permissions: { type: "array", items: { type: "string" } }
},
if: {
properties: { type: { const: "admin" } }
},
then: {
required: ["permissions"]
},
else: {
not: {
required: ["permissions"]
}
}
};
Здесь наличие поля permissions зависит от значения
type. Такая схема позволяет описывать бизнес-логику прямо
на уровне структуры данных.
Ajv компилирует такие конструкции в эффективный код без интерпретации на каждом вызове, что делает их пригодными для высоконагруженных систем.
Когда требования касаются не значений, а структуры объекта, применяются зависимые ключи.
Позволяет указать, какие поля становятся обязательными при наличии другого поля.
const schema = {
type: "object",
properties: {
creditCard: { type: "string" },
billingAddress: { type: "string" }
},
dependentRequired: {
creditCard: ["billingAddress"]
}
};
Если присутствует creditCard, то обязательно должно быть
и billingAddress.
Более гибкий механизм, позволяющий привязывать целую под-схему к наличию поля.
const schema = {
type: "object",
properties: {
country: { type: "string" }
},
dependentSchemas: {
country: {
properties: {
zipCode: { type: "string" }
},
required: ["zipCode"]
}
}
};
При наличии country структура объекта расширяется
дополнительными требованиями.
Ajv поддерживает расширение $data, которое позволяет
ссылаться на значения других полей внутри схемы. Это открывает
возможность создания зависимостей не только на уровне структуры, но и на
уровне значений.
Для активации используется опция:
const ajv = new Ajv({ $data: true });
Пример:
const schema = {
type: "object",
properties: {
min: { type: "number" },
max: {
type: "number",
minimum: { $data: "1/min" }
}
}
};
Значение max динамически зависит от min.
Это особенно важно для диапазонов, временных интервалов, ограничений
количества.
Логические операторы JSON Schema часто используются для построения условных моделей данных.
Объединяет несколько схем, каждая из которых должна быть валидна.
const schema = {
allOf: [
{
properties: {
role: { const: "user" }
}
},
{
properties: {
active: { type: "boolean" }
}
}
]
};
Используется, когда допустимы разные альтернативные структуры.
const schema = {
anyOf: [
{ required: ["email"] },
{ required: ["phone"] }
]
};
Строгая альтернатива — должен соответствовать только одному варианту.
Эти конструкции часто становятся основой динамической логики, когда структура данных определяется набором допустимых сценариев.
В реальных приложениях часто требуется генерация схем на основе параметров. Ajv не ограничивает способ создания схемы, поэтому распространён подход фабрик:
const createSchema = (role) => {
return {
type: "object",
properties: {
id: { type: "string" },
role: { const: role }
},
required: ["id", "role"]
};
};
Такая схема формируется на этапе выполнения и может учитывать конфигурацию системы, права доступа или внешние данные.
Одним из наиболее мощных механизмов Ajv является возможность создания
собственных ключей через addKeyword. Это позволяет
реализовывать сложную динамическую логику, недоступную в стандартной
JSON Schema.
ajv.addKeyword({
keyword: "isPositiveIf",
type: "number",
schemaType: "boolean",
compile: (schema) => {
return function (data, dataPath, parentData) {
if (schema && parentData.flag) {
return data > 0;
}
return true;
};
}
});
Использование:
const schema = {
type: "object",
properties: {
value: { isPositiveIf: true },
flag: { type: "boolean" }
}
};
Пользовательские ключи позволяют переносить бизнес-логику в слой валидации, сохраняя при этом декларативный стиль описания данных.
Ajv поддерживает макросы, которые трансформируют пользовательские ключи в стандартные схемы JSON Schema на этапе компиляции.
ajv.addKeyword({
keyword: "range",
macro: (schema) => {
return {
minimum: schema[0],
maximum: schema[1]
};
}
});
Это позволяет расширять язык схем без потери производительности, так как итоговая структура остаётся валидной JSON Schema.
В некоторых случаях требуется учитывать внешние параметры, не входящие в сам объект данных. Это реализуется через параметры функции validate или через фабрики схем.
const validate = ajv.compile({
type: "object",
properties: {
limit: { type: "number", maximum: 100 }
}
});
const result = validate(data);
Для динамических ограничений схема может генерироваться на основе окружения:
const schema = {
type: "object",
properties: {
quota: { type: "number", maximum: config.maxQuota }
}
};
Динамические требования часто проявляются в массивах, где ограничения зависят от содержимого элементов.
const schema = {
type: "array",
items: {
type: "object",
properties: {
type: { enum: ["A", "B"] }
},
if: {
properties: { type: { const: "A" } }
},
then: {
required: ["valueA"]
},
else: {
required: ["valueB"]
}
}
};
Каждый элемент массива проходит собственную ветвящуюся логику.
Ajv в строгом режиме (strict: true) накладывает
ограничения на использование невалидных или неоднозначных конструкций.
Это особенно важно для динамических схем, где часто возникают:
Строгий режим позволяет выявлять ошибки на этапе компиляции схемы, а не во время выполнения.
При использовании условных и зависимых конструкций структура ошибок становится вложенной. Ajv сохраняет путь до конкретного условия, которое не было выполнено.
Пример типичного результата:
Это важно для построения интерфейсов, где требуется точная диагностика ошибок формы.
На практике выделяются несколько устойчивых подходов:
Комбинация этих подходов позволяет описывать сложные модели данных без перехода к императивной логике.
Несмотря на выразительность, динамические конструкции могут влиять на производительность при неправильном использовании. Основные факторы:
Ajv компенсирует это предварительной компиляцией схем в оптимизированные функции, однако стоимость компиляции остаётся значимой при частой пересборке схем.
Сложные системы часто строятся через композицию:
Такой подход позволяет отделять стабильные и изменяемые части модели данных, сохраняя управляемость схемы при росте сложности.