Валидация данных в реальных приложениях редко бывает статичной: значение одного поля часто зависит от другого, а допустимость параметра определяется контекстом запроса. В Joi такие сценарии реализуются через условные схемы, позволяющие описывать динамическое поведение валидатора на основе значений внутри объекта или внешних параметров.
Основной инструмент для построения условий — метод
.when(). Он применяется к конкретному полю и позволяет
менять его схему в зависимости от значения другого поля.
Простейший пример зависимости:
const schema = Joi.object({
role: Joi.string().valid('user', 'admin').required(),
accessCode: Joi.string().when('role', {
is: 'admin',
then: Joi.required(),
otherwise: Joi.optional()
})
});
В этом случае поле accessCode становится обязательным
только если role === 'admin'.
Ключевая идея заключается в разделении схемы на ветви:
is — условие сравненияthen — схема, применяемая при выполнении условияotherwise — схема по умолчаниюУсловие может быть расширено до набора значений:
const schema = Joi.object({
status: Joi.string().valid('draft', 'published', 'archived'),
publishedAt: Joi.date().when('status', {
is: Joi.valid('published', 'archived'),
then: Joi.required(),
otherwise: Joi.forbidden()
})
});
Здесь используется вложенный валидатор Joi.valid(),
который позволяет проверять принадлежность к множеству.
Joi поддерживает цепочки условий через массив правил:
const schema = Joi.object({
plan: Joi.string().valid('free', 'pro', 'enterprise'),
storageLimit: Joi.number().when('plan', [
{
is: 'free',
then: Joi.valid(5)
},
{
is: 'pro',
then: Joi.valid(50)
},
{
is: 'enterprise',
then: Joi.valid(500)
}
])
});
Такая конструкция позволяет описывать множественные ветвления без вложенных конструкций.
Условные схемы часто используются для проверки связей между несколькими полями одного объекта.
const schema = Joi.object({
password: Joi.string().required(),
confirmPassword: Joi.string().when('password', {
is: Joi.exist(),
then: Joi.valid(Joi.ref('password')).required(),
otherwise: Joi.forbidden()
})
});
Здесь используется ссылка Joi.ref, позволяющая
сравнивать значения разных полей.
Одно из наиболее частых применений — динамическое определение обязательности поля:
const schema = Joi.object({
deliveryType: Joi.string().valid('courier', 'pickup'),
address: Joi.string().when('deliveryType', {
is: 'courier',
then: Joi.required(),
otherwise: Joi.optional()
})
});
Логика обязательности становится частью схемы, а не внешней проверки.
Условия могут зависеть сразу от нескольких полей через
switch-подобные конструкции:
const schema = Joi.object({
userType: Joi.string().valid('guest', 'registered'),
age: Joi.number(),
discount: Joi.number().when(Joi.object({
userType: Joi.valid('registered'),
age: Joi.number().min(18)
}).unknown(), {
then: Joi.valid(10),
otherwise: Joi.valid(0)
})
});
Здесь используется объектное условие, где проверяется сразу несколько значений.
Помимо внутренних полей объекта, Joi позволяет использовать внешние значения через контекст:
const schema = Joi.object({
price: Joi.number().when('$currency', {
is: 'KZT',
then: Joi.max(100000),
otherwise: Joi.max(1000)
})
});
При валидации передаётся контекст:
schema.validate(data, { context: { currency: 'KZT' } });
Это расширяет область применения условных схем за пределы одного объекта.
Условия применимы и к структурам внутри массивов:
const schema = Joi.object({
users: Joi.array().items(
Joi.object({
type: Joi.string().valid('admin', 'user'),
permissions: Joi.array().when('type', {
is: 'admin',
then: Joi.required(),
otherwise: Joi.forbidden()
})
})
)
});
Каждый элемент массива получает собственную условную логику.
Помимо .when(), Joi предоставляет
Joi.alternatives(), позволяющий описывать несколько
возможных схем:
const schema = Joi.alternatives().conditional('type', [
{
is: 'email',
then: Joi.string().email()
},
{
is: 'phone',
then: Joi.string().pattern(/^[0-9]+$/)
}
]);
Этот подход используется, когда структура данных радикально меняется в зависимости от условия.
Условия могут быть вложенными, создавая многоуровневую логику:
const schema = Joi.object({
subscription: Joi.string().valid('basic', 'pro'),
features: Joi.object({
analytics: Joi.boolean().when('subscription', {
is: 'pro',
then: Joi.valid(true),
otherwise: Joi.valid(false)
})
})
});
Каждый уровень объекта может иметь собственные зависимости.
Условная схема может сочетаться с преобразованиями:
const schema = Joi.object({
role: Joi.string(),
permissions: Joi.array().when('role', {
is: 'admin',
then: Joi.array().items(Joi.string().uppercase()),
otherwise: Joi.array().items(Joi.string().lowercase())
})
});
Таким образом условие влияет не только на валидность, но и на обработку данных.
При построении сложных схем важно учитывать порядок вычисления:
Joi.ref.when()Ошибки в логике условий часто возникают из-за неправильной интерпретации значений на момент валидации.
На практике условные схемы часто усложняются без необходимости, что приводит к:
required и forbiddenНаиболее проблемные случаи связаны с одновременным использованием
нескольких .when() на одном поле, где порядок правил
начинает влиять на итоговую схему.
Схемы можно выносить и комбинировать:
const baseEmail = Joi.string().email();
const schema = Joi.object({
contactMethod: Joi.string().valid('email', 'sms'),
contact: baseEmail.when('contactMethod', {
is: 'email',
then: Joi.required(),
otherwise: Joi.forbidden()
})
});
Это позволяет повторно использовать базовые определения и накладывать на них условия без дублирования логики.