Required и optional

Валидация данных в Joi строится вокруг описания схемы объекта, где каждое поле может быть строго обязательным или опциональным. Управление этим поведением — один из ключевых аспектов построения надежных API и форм, так как именно здесь определяется, какие данные система обязана получить, а какие может принять частично или проигнорировать.

Поведение по умолчанию

В Joi каждое свойство схемы по умолчанию считается необязательным, если явно не указано обратное. Это означает, что отсутствие поля в объекте не приведёт к ошибке валидации.

const schema = Joi.object({
  name: Joi.string(),
  age: Joi.number()
});

В этом примере объект может быть пустым:

{}

и это будет считаться корректным результатом валидации.

Такое поведение важно учитывать при проектировании структур данных, поскольку отсутствие явной обязательности может привести к неожиданно «разреженным» объектам.


Метод required()

Метод .required() делает поле строго обязательным. Если поле отсутствует в проверяемом объекте, валидация завершится ошибкой.

const schema = Joi.object({
  name: Joi.string().required(),
  age: Joi.number().required()
});

Корректный объект:

{
  name: "Ivan",
  age: 25
}

Некорректные варианты:

{
  name: "Ivan"
}

или

{}

При использовании .required() важно учитывать, что проверка происходит именно на уровне наличия ключа в объекте, а не только его значения.


Явное обозначение optional()

Метод .optional() используется для явного указания необязательности поля. Хотя это поведение совпадает с дефолтным, его применение повышает читаемость схемы.

const schema = Joi.object({
  name: Joi.string().required(),
  nickname: Joi.string().optional()
});

Здесь nickname может отсутствовать без нарушения схемы.

Использование .optional() особенно полезно в больших схемах, где требуется явно разграничивать обязательные и необязательные данные.


Взаимодействие required и 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() на обязательные поля

Метод .unknown() в объектных схемах влияет на поведение, но не изменяет обязательность уже описанных полей.

const schema = Joi.object({
  name: Joi.string().required()
}).unknown(true);

Это означает:

  • name всё ещё обязателен
  • дополнительные поля допускаются

Важно различать:

  • обязательность ключей (required/optional)
  • допустимость неизвестных ключей (unknown)

Динамическая обязательность

В 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() и логическая необязательность

Метод .strip() не влияет напрямую на обязательность, но может удалять поля из результата валидации.

const schema = Joi.object({
  temp: Joi.string().strip(),
  name: Joi.string().required()
});

Если поле temp присутствует, оно будет удалено из результата, но отсутствие его не вызовет ошибки.

Это создаёт эффект «логически необязательного» поля, даже если оно приходит от клиента.


Практическое значение обязательности

Контроль обязательных и необязательных полей определяет:

  • целостность входных данных
  • предсказуемость API
  • устойчивость к ошибкам клиента
  • читаемость контрактов между сервисами

Жёстко обязательные поля формируют минимальный контракт данных, тогда как необязательные позволяют расширять функциональность без ломки совместимости.


Типичные ошибки при использовании required и optional

Частые проблемы при работе с Joi:

  • ожидание, что .optional() изменяет поведение по умолчанию (на самом деле это лишь явное указание)
  • неверное понимание вложенной обязательности
  • смешивание .required() и .allow(null) без учёта их различий
  • отсутствие явного описания схемы, из-за чего структура становится неоднозначной

Поведение null и отсутствие поля

Важно различать два состояния:

  • поле отсутствует
  • поле присутствует со значением null
Joi.string().required()

не допускает отсутствия поля, но может потребовать дополнительной настройки для обработки null, например:

Joi.string().required().allow(null)

В этом случае поле обязательно по наличию, но допускает значение null.


Итоговые принципы проектирования схем

При использовании обязательных и необязательных полей в Joi ключевым становится не синтаксис, а структура контракта данных:

  • обязательные поля формируют минимально допустимую модель
  • необязательные поля расширяют модель без нарушения совместимости
  • явное указание optional повышает читаемость сложных схем
  • вложенные объекты требуют отдельного контроля обязательности на каждом уровне
  • условная логика через when позволяет строить адаптивные схемы

Грамотное сочетание этих механизмов позволяет формировать строгие, но гибкие схемы валидации, устойчивые к изменению требований и расширению структуры данных.