alternatives().match позволяет задавать набор схем и выбирать одну из них в зависимости от структуры входных данных. Это механизм условной маршрутизации валидации, где данные не приводятся к единому формату заранее, а проверяются по нескольким альтернативным моделям.
Механизм основан на последовательной проверке входного значения против набора схем с условиями сопоставления. Каждая схема описывает собственное правило валидации, а выбор осуществляется автоматически на основании совпадения с заданными критериями.
Ключевая особенность заключается в том, что проверка не останавливается на первом подходящем типе, если явно не указано поведение. Вместо этого Joi анализирует структуру данных и сопоставляет её с условиями, заданными для каждой альтернативы.
Общий вид конструкции:
const schema = Joi.alternatives().match('one')
.try(
Joi.string().min(3),
Joi.number().integer(),
Joi.object({
id: Joi.number().required()
})
);
В этом примере выбор схемы происходит автоматически, но match управляет стратегией сопоставления.
Режим one означает, что данные должны соответствовать ровно одной схеме из набора. Если совпадений нет или их больше одного, валидация считается неуспешной.
const schema = Joi.alternatives().match('one').try(
Joi.string().pattern(/^[a-z]+$/),
Joi.number().positive(),
Joi.object({ type: Joi.string().valid('A') })
);
Поведение:
Режим any допускает соответствие хотя бы одной схеме. Это более мягкая стратегия, где достаточно одного успешного совпадения.
const schema = Joi.alternatives().match('any').try(
Joi.string().min(5),
Joi.string().pattern(/^\d+$/)
);
Значение “123” пройдёт вторую схему, даже если не подходит под первую.
Режим all требует соответствия всем схемам одновременно. Используется редко, но полезен при наложении независимых ограничений.
const schema = Joi.alternatives().match('all').try(
Joi.string().min(3),
Joi.string().pattern(/[a-z]/)
);
Строка должна удовлетворять обеим проверкам одновременно.
На практике alternatives().match применяется для различения форматов входных данных. Типичный сценарий — различие между примитивами и объектами.
const schema = Joi.alternatives().match('one').try(
Joi.string(),
Joi.object({
value: Joi.string().required()
})
);
В этом случае строка обрабатывается как отдельный вариант, а объект — как структурированный контейнер.
В сложных схемах match используется для эмуляции условных операторов. Это позволяет отказаться от вложенных when и построить линейную структуру правил.
const schema = Joi.alternatives().match('one').try(
Joi.object({
kind: Joi.valid('user'),
name: Joi.string().required()
}),
Joi.object({
kind: Joi.valid('admin'),
permissions: Joi.array().items(Joi.string()).required()
})
);
Поле kind здесь играет роль дискриминатора, определяющего выбор схемы.
Порядок передачи схем влияет на результат в режимах, где возможны пересечения. Если одно значение удовлетворяет нескольким схемам, возникает конфликт выбора.
const schema = Joi.alternatives().match('one').try(
Joi.string().min(0),
Joi.string().max(10)
);
Любая строка длиной до 10 символов удовлетворяет обеим схемам, что делает результат неоднозначным.
match часто применяется для сложных структур API-ответов, где формат зависит от типа операции.
const schema = Joi.alternatives().match('one').try(
Joi.object({
event: Joi.valid('login'),
userId: Joi.number().required()
}),
Joi.object({
event: Joi.valid('error'),
message: Joi.string().required(),
code: Joi.number().required()
})
);
Такой подход позволяет описывать несколько контрактов в одной точке схемы.
Хотя match заменяет часть логики when, их можно использовать совместно. when чаще применяется внутри одной схемы, тогда как match работает на уровне альтернатив.
const schema = Joi.alternatives().match('one').try(
Joi.object({
type: Joi.valid('text'),
payload: Joi.string()
}),
Joi.object({
type: Joi.valid('binary'),
payload: Joi.binary()
})
);
Здесь match заменяет сложную цепочку условий.
Типичные причины ошибок:
const schema = Joi.alternatives().match('one').try(
Joi.any(),
Joi.string()
);
Такая конструкция почти всегда приводит к неоднозначности.
При проектировании важно:
Структурирование схем через match особенно эффективно в API, где один эндпоинт возвращает разные типы объектов в зависимости от состояния системы.
Joi выполняет кастинг значений до проверки, что может влиять на выбор альтернативы. Например, строка “123” может быть преобразована в число при некоторых настройках, что изменит результат сопоставления.
const schema = Joi.alternatives().match('one').try(
Joi.number(),
Joi.string()
);
Без отключения преобразований строка “123” может попасть в числовую ветку.
При увеличении количества схем match становится инструментом композиции. Вместо глубокой вложенности условий формируется плоский список правил.
const schema = Joi.alternatives().match('one').try(
Joi.object({ event: Joi.valid('create'), data: Joi.object() }),
Joi.object({ event: Joi.valid('update'), data: Joi.object() }),
Joi.object({ event: Joi.valid('delete'), id: Joi.number() })
);
Такой подход упрощает поддержку и расширение логики без изменения существующих веток.