Условная валидация в Joi строится вокруг идеи зависимости одного поля от значения другого. Это позволяет описывать схемы, в которых структура и обязательность данных изменяются динамически, в зависимости от контекста запроса или взаимосвязанных параметров.
Основной инструмент условной логики в Joi — метод
.when(). Он применяется к полю схемы и позволяет задавать
поведение в зависимости от другого поля.
Синтаксис опирается на указание ссылки на зависимое поле и набор условий:
const schema = Joi.object({
role: Joi.string().valid('admin', 'user'),
permissions: Joi.array().when('role', {
is: 'admin',
then: Joi.required(),
otherwise: Joi.forbidden()
})
});
В этом примере поле permissions становится обязательным
только при значении role = admin. Во всех остальных случаях
оно запрещено.
Ключевые элементы конструкции:
Joi.refДля более гибких сценариев применяется Joi.ref,
позволяющий ссылаться на значения других полей динамически.
const schema = Joi.object({
password: Joi.string().min(8),
confirmPassword: Joi.any().valid(Joi.ref('password')).required()
});
Здесь confirmPassword должен совпадать с
password. Это типичный пример межполевой зависимости без
явного when.
when поддерживает сложные конструкции с альтернативными
условиями через массив:
const schema = Joi.object({
type: Joi.string().valid('email', 'phone'),
contact: Joi.string().when('type', [
{
is: 'email',
then: Joi.string().email().required()
},
{
is: 'phone',
then: Joi.string().pattern(/^\d+$/).required()
}
])
});
Такой подход позволяет описывать ветвление логики, аналогичное
switch-case на уровне схемы.
Joi допускает каскадные условия, когда одно поле зависит от другого, а тот — от третьего:
const schema = Joi.object({
accountType: Joi.string().valid('personal', 'business'),
companyName: Joi.string().when('accountType', {
is: 'business',
then: Joi.required().when('country', {
is: 'US',
then: Joi.string().min(2),
otherwise: Joi.string().min(3)
}),
otherwise: Joi.forbidden()
}),
country: Joi.string()
});
Здесь поведение поля изменяется не только от одного параметра, но и от комбинации нескольких факторов.
alternatives().conditionalАльтернативный способ выражения условий реализуется через
Joi.alternatives(), который позволяет строить набор
возможных схем:
const schema = Joi.object({
paymentMethod: Joi.string().valid('card', 'paypal'),
paymentDetails: Joi.alternatives().conditional('paymentMethod', {
switch: [
{
is: 'card',
then: Joi.object({
cardNumber: Joi.string().creditCard().required(),
cvv: Joi.string().length(3).required()
})
},
{
is: 'paypal',
then: Joi.object({
email: Joi.string().email().required()
})
}
]
})
});
В этом случае структура объекта полностью меняется в зависимости от
значения paymentMethod.
required()Часто используется паттерн, при котором поле становится обязательным только при выполнении условия:
const schema = Joi.object({
hasDiscount: Joi.boolean(),
discountCode: Joi.string().when('hasDiscount', {
is: true,
then: Joi.required(),
otherwise: Joi.optional()
})
});
Такой подход формирует декларативную зависимость без необходимости ручной проверки в коде приложения.
Условие может срабатывать не только по значению, но и по его отсутствию:
const schema = Joi.object({
referralCode: Joi.string(),
signupBonus: Joi.number().when('referralCode', {
is: Joi.exist(),
then: Joi.valid(0),
otherwise: Joi.number().min(10)
})
});
Здесь логика зависит от того, присутствует ли поле в объекте.
.context()В сложных сценариях условие может опираться не только на поля объекта, но и на внешний контекст:
const schema = Joi.object({
age: Joi.number().when(Joi.ref('$isAdmin'), {
is: true,
then: Joi.optional(),
otherwise: Joi.required()
})
});
Контекст передаётся при валидации:
schema.validate(data, { context: { isAdmin: true } });
Условные конструкции применимы и к массивам:
const schema = Joi.object({
items: Joi.array().items(Joi.object({
type: Joi.string().valid('digital', 'physical'),
downloadLink: Joi.string().when('type', {
is: 'digital',
then: Joi.required(),
otherwise: Joi.forbidden()
})
}))
});
Каждый элемент массива может иметь собственную условную логику.
Joi позволяет строить взаимоисключающие или зависимые поля:
const schema = Joi.object({
startDate: Joi.date(),
endDate: Joi.date().greater(Joi.ref('startDate'))
});
Здесь второе поле зависит от значения первого и должно быть строго больше.
Другой пример взаимного исключения:
const schema = Joi.object({
email: Joi.string(),
phone: Joi.string()
}).or('email', 'phone');
Это задаёт правило: должно быть указано хотя бы одно из полей.
Сложные схемы часто требуют объединения нескольких механизмов:
const schema = Joi.object({
role: Joi.string().valid('admin', 'editor'),
accessLevel: Joi.number().when('role', {
switch: [
{
is: 'admin',
then: Joi.number().min(10).required()
},
{
is: 'editor',
then: Joi.number().min(5).required()
}
]
})
});
Такой подход делает схему масштабируемой при росте количества условий.
При работе с условной логикой часто возникают следующие проблемы:
isotherwise, приводящее к неопределённому
поведениюwhen, усложняющая поддержку
схемКорректное проектирование схем требует минимизации глубины вложенных
условий и выделения повторяющихся правил в отдельные фрагменты схемы
через .concat() или вспомогательные схемы.
Условная валидация в Joi формирует основу для описания динамических структур данных, где правила проверки становятся частью декларативного описания модели, а не императивной логики приложения.