Валидация структуры объектов в Joi часто требует не только проверки
типов и форматов, но и контроля за наличием ключей в зависимости от
контекста данных. Механизм has позволяет задавать
обязательное присутствие определённых свойств объекта, а
single управляет интерпретацией одиночных значений как
массивов, что особенно важно при работе с API, где входные данные могут
быть непоследовательными.
Метод has применяется к схемам объектов и задаёт
условие, при котором объект обязан содержать определённый ключ или набор
ключей. Это особенно полезно в динамических структурах, где часть полей
может быть опциональной, но наличие некоторых комбинаций строго
регламентировано.
const schema = Joi.object({
name: Joi.string(),
age: Joi.number()
}).has('name');
В данном случае объект обязан содержать ключ name,
независимо от того, присутствует ли age. Если
name отсутствует, валидация завершится ошибкой.
Метод поддерживает передачу массива ключей, что позволяет задавать требования к группе полей:
const schema = Joi.object({
username: Joi.string(),
password: Joi.string(),
email: Joi.string()
}).has(['username', 'password']);
Такое правило требует одновременного наличия всех указанных ключей. Отсутствие хотя бы одного из них делает объект невалидным.
has может принимать не только имя ключа, но и схему
валидации, которая применяется к значению этого ключа:
const schema = Joi.object({
role: Joi.string(),
permissions: Joi.array()
}).has('permissions', Joi.array().min(1));
Здесь проверяется не только наличие permissions, но и
то, что массив содержит минимум один элемент. Такой подход позволяет
связывать структурные и содержательные ограничения.
has не заменяет required, а дополняет его.
Поля могут оставаться опциональными, но их совместное присутствие
регулируется логикой схемы.
const schema = Joi.object({
a: Joi.string(),
b: Joi.string(),
c: Joi.string()
}).has('a').has('b');
В этом случае объект обязан содержать как минимум a и
b, тогда как c остаётся необязательным.
Метод single применяется к схемам массивов и изменяет
поведение валидации таким образом, что одиночное значение автоматически
трактуется как массив с одним элементом. Это решает распространённую
проблему API, где одно и то же поле может приходить как строка или как
массив строк.
const schema = Joi.array().items(Joi.string()).single();
Такое определение означает:
Схема:
const schema = Joi.array().items(Joi.number()).single();
Валидные значения:
[1, 2, 3] // остаётся массивом
5 // преобразуется в [5]
При интеграции с внешними API часто встречается смешанный формат данных:
"tag": "news""tag": ["news", "sports"]С single это обрабатывается единообразно:
const schema = Joi.array().items(Joi.string()).single();
Результат после валидации всегда будет массивом, что упрощает дальнейшую обработку.
single не отменяет проверку элементов массива:
const schema = Joi.array()
.items(Joi.object({
id: Joi.number().required()
}))
.single();
Одиночный объект будет преобразован в массив, но структура объекта
всё равно проверяется в соответствии с items.
В сложных моделях данных оба механизма могут применяться
одновременно, поскольку они решают разные задачи: has
управляет структурой объекта, а single нормализует массивы
внутри него.
const schema = Joi.object({
user: Joi.object({
name: Joi.string().required(),
roles: Joi.array().items(Joi.string()).single()
}).has('name')
});
В этом примере:
user обязан содержать name;roles может быть строкой или массивом строк;roles приводится к массиву.По умолчанию Joi выполняет преобразования входных данных
(convert: true). Это напрямую влияет на поведение
single, так как приведение одиночного значения к массиву
происходит на этапе преобразования.
При отключённом преобразовании:
Joi.array().single().prefs({ convert: false })
значение останется в исходном виде, и проверка может завершиться ошибкой, если ожидается массив.
Если ключ не указан в объекте, правило has не допускает
частичного игнорирования — объект считается невалидным даже при наличии
других корректных полей.
single не предотвращает появление пустого массива:
Joi.array().items(Joi.string()).single()
значение [] остаётся допустимым, если не заданы
дополнительные ограничения (min, required,
length).
В схемах массивов порядок цепочки важен:
Joi.array().single().items(Joi.string())
и
Joi.array().items(Joi.string()).single()
в большинстве случаев эквивалентны, но при сложных кастомных правилах и расширениях поведение может различаться из-за этапов обработки (конвертация, затем проверка элементов).
Механизм has часто применяется для моделирования
зависимостей между полями, когда наличие одного ключа подразумевает
обязательность других компонентов структуры.
Механизм single стабилизирует входные данные, снижая
необходимость дополнительной нормализации на уровне бизнес-логики. Это
особенно важно при обработке запросов, где клиентские реализации не
гарантируют единообразный формат передачи массивов.
При вложенных структурах has действует локально для
каждого объекта:
const schema = Joi.object({
profile: Joi.object({
firstName: Joi.string(),
lastName: Joi.string()
}).has('firstName')
});
Проверка применяется только к объекту profile, не
затрагивая родительский уровень.
Ошибки, возникающие при нарушении has, обычно связаны с
отсутствием ключа и описываются как требования обязательного свойства
объекта.
Ошибки, связанные с single, чаще возникают при конфликте
типов, когда преобразование отключено или входные данные невозможно
интерпретировать как массив или одиночное значение допустимого типа.