Валидация данных в Joi строится вокруг описания схемы объекта, где каждое поле может быть строго обязательным или опциональным. Управление этим поведением — один из ключевых аспектов построения надежных API и форм, так как именно здесь определяется, какие данные система обязана получить, а какие может принять частично или проигнорировать.
В Joi каждое свойство схемы по умолчанию считается необязательным, если явно не указано обратное. Это означает, что отсутствие поля в объекте не приведёт к ошибке валидации.
const schema = Joi.object({
name: Joi.string(),
age: Joi.number()
});
В этом примере объект может быть пустым:
{}
и это будет считаться корректным результатом валидации.
Такое поведение важно учитывать при проектировании структур данных, поскольку отсутствие явной обязательности может привести к неожиданно «разреженным» объектам.
Метод .required() делает поле строго обязательным. Если
поле отсутствует в проверяемом объекте, валидация завершится
ошибкой.
const schema = Joi.object({
name: Joi.string().required(),
age: Joi.number().required()
});
Корректный объект:
{
name: "Ivan",
age: 25
}
Некорректные варианты:
{
name: "Ivan"
}
или
{}
При использовании .required() важно учитывать, что
проверка происходит именно на уровне наличия ключа в объекте, а не
только его значения.
Метод .optional() используется для явного указания
необязательности поля. Хотя это поведение совпадает с дефолтным, его
применение повышает читаемость схемы.
const schema = Joi.object({
name: Joi.string().required(),
nickname: Joi.string().optional()
});
Здесь nickname может отсутствовать без нарушения
схемы.
Использование .optional() особенно полезно в больших
схемах, где требуется явно разграничивать обязательные и необязательные
данные.
Логика приоритетов в Joi проста: если к полю применён
.required(), он всегда имеет приоритет над
.optional() при конфликте цепочек методов.
Joi.string().optional().required()
Результат: поле будет обязательным.
Это связано с тем, что .required() изменяет базовое
состояние схемы, а .optional() лишь уточняет поведение по
умолчанию.
В объектах поведение обязательности определяется на уровне каждого ключа отдельно.
const userSchema = Joi.object({
id: Joi.number().required(),
email: Joi.string().email().required(),
phone: Joi.string().optional(),
address: Joi.string()
});
Здесь:
id и email обязательныphone явно необязателенaddress необязателен по умолчаниюТакая схема часто используется в API-ответах, где часть данных может отсутствовать в зависимости от контекста.
Вложенные структуры требуют отдельного контроля обязательности как на уровне родительского объекта, так и на уровне внутренних полей.
const schema = Joi.object({
user: Joi.object({
name: Joi.string().required(),
age: Joi.number()
}).required()
});
Здесь:
user обязателенuser.name обязателенuser.age необязателенЕсли отсутствует весь объект user, валидация завершится
ошибкой ещё до проверки внутренних полей.
Метод .unknown() в объектных схемах влияет на поведение,
но не изменяет обязательность уже описанных полей.
const schema = Joi.object({
name: Joi.string().required()
}).unknown(true);
Это означает:
name всё ещё обязателенВажно различать:
В Joi возможно изменение обязательности в зависимости от условий
через .when().
const schema = Joi.object({
isCompany: Joi.boolean(),
companyName: Joi.string().when('isCompany', {
is: true,
then: Joi.required(),
otherwise: Joi.optional()
})
});
Здесь companyName становится обязательным только при
isCompany === true.
Такой подход применяется в формах с условной логикой, где структура данных зависит от пользовательского выбора.
Метод .strip() не влияет напрямую на обязательность, но
может удалять поля из результата валидации.
const schema = Joi.object({
temp: Joi.string().strip(),
name: Joi.string().required()
});
Если поле temp присутствует, оно будет удалено из
результата, но отсутствие его не вызовет ошибки.
Это создаёт эффект «логически необязательного» поля, даже если оно приходит от клиента.
Контроль обязательных и необязательных полей определяет:
Жёстко обязательные поля формируют минимальный контракт данных, тогда как необязательные позволяют расширять функциональность без ломки совместимости.
Частые проблемы при работе с Joi:
.optional() изменяет поведение по
умолчанию (на самом деле это лишь явное указание).required() и .allow(null) без
учёта их различийВажно различать два состояния:
nullJoi.string().required()
не допускает отсутствия поля, но может потребовать дополнительной
настройки для обработки null, например:
Joi.string().required().allow(null)
В этом случае поле обязательно по наличию, но допускает значение
null.
При использовании обязательных и необязательных полей в Joi ключевым становится не синтаксис, а структура контракта данных:
Грамотное сочетание этих механизмов позволяет формировать строгие, но гибкие схемы валидации, устойчивые к изменению требований и расширению структуры данных.