В библиотеке валидации Joi ключевую роль играет возможность описывать
данные, которые могут соответствовать разным структурам. Для этого
используется alternatives(), позволяющий задать набор
допустимых схем и выбрать ту, которая подходит входным данным.
alternatives() описывает значение, которое может
соответствовать одной из нескольких схем. Это полезно в ситуациях, когда
формат данных не фиксирован:
const Joi = require('joi');
const schema = Joi.alternatives().try(
Joi.string(),
Joi.number()
);
В этом случае значение считается валидным, если оно является строкой или числом.
const schema = Joi.alternatives(
Joi.string(),
Joi.number()
);
Обе формы эквивалентны, но .try() делает намерение более
явным.
По умолчанию alternatives() работает в режиме:
any — достаточно, чтобы совпала хотя бы одна схема.
const schema = Joi.alternatives().try(
Joi.string().min(3),
Joi.number().integer()
);
schema.validate('abc'); // valid
schema.validate(123); // valid
schema.validate('a'); // invalid
Метод match() позволяет изменить логику сопоставления
альтернатив.
Достаточно совпадения с одной схемой.
Joi.alternatives().match('any').try(
Joi.string(),
Joi.number()
);
Значение должно соответствовать только одной из альтернатив.
const schema = Joi.alternatives().match('one').try(
Joi.string().max(5),
Joi.string().alphanum()
);
Если строка подходит сразу под обе схемы, будет ошибка.
Редкий режим, при котором значение должно удовлетворять каждой схеме.
const schema = Joi.alternatives().match('all').try(
Joi.string().min(3),
Joi.string().max(10)
);
Фактически используется для наложения нескольких ограничений через разные схемы.
alternatives() часто применяется для описания разных
форм объекта.
const schema = Joi.alternatives().try(
Joi.object({
type: Joi.string().valid('A').required(),
value: Joi.number().required()
}),
Joi.object({
type: Joi.string().valid('B').required(),
value: Joi.string().required()
})
);
Здесь структура зависит от значения поля type.
Хотя alternatives() используется напрямую, чаще
применяется вместе с when().
const schema = Joi.object({
type: Joi.string().required(),
value: Joi.alternatives().conditional('type', {
is: 'number',
then: Joi.number(),
otherwise: Joi.string()
})
});
Это позволяет динамически выбирать схему.
const schema = Joi.alternatives().conditional('mode', [
{
is: 'strict',
then: Joi.object({
data: Joi.string().required()
})
},
{
is: 'loose',
then: Joi.object({
data: Joi.any()
})
}
]);
Такой подход часто используется для конфигурационных структур.
alternatives() можно применять внутри массивов для
гибридных структур:
const schema = Joi.array().items(
Joi.alternatives().try(
Joi.string(),
Joi.number(),
Joi.object({ id: Joi.number() })
)
);
Массив может содержать элементы разных типов.
const schema = Joi.object({
payload: Joi.alternatives().try(
Joi.object({
kind: Joi.string().valid('text'),
value: Joi.string()
}),
Joi.object({
kind: Joi.string().valid('binary'),
value: Joi.binary()
})
)
});
Joi.alternatives().match('one').try(
Joi.number().integer(),
Joi.number().min(0)
);
Число 5 удовлетворяет обеим схемам → ошибка.
Joi.alternatives().try(
Joi.any(),
Joi.string()
);
Первая схема перекрывает вторую, делая её бессмысленной.
Использование alternatives() увеличивает стоимость
проверки, поскольку:
Оптимизация достигается за счёт:
when() вместо больших try-цепочекconst schema = Joi.alternatives().try(
Joi.object({
version: Joi.string().valid('1'),
data: Joi.string()
}),
Joi.object({
version: Joi.string().valid('2'),
payload: Joi.object()
})
);
const responseSchema = Joi.alternatives().try(
Joi.object({ success: Joi.boolean().valid(true) }),
Joi.object({ success: Joi.boolean().valid(false), error: Joi.string() })
);
const inputSchema = Joi.alternatives().try(
Joi.string().email(),
Joi.string().pattern(/^\+\d{10,15}$/)
);