В Joi значения по умолчанию задаются через метод
default(), который позволяет автоматически подставлять
значение в случае отсутствия поля в входных данных или при его явной
неопределённости (undefined). Этот механизм является
ключевым инструментом при построении устойчивых схем валидации, так как
позволяет не только валидировать данные, но и нормализовать их структуру
до единого ожидаемого формата.
Метод default() принимает значение, которое будет
использовано, если входное значение отсутствует.
import Joi from 'joi';
const schema = Joi.object({
role: Joi.string().default('user')
});
schema.validate({});
// результат: { role: 'user' }
Если поле присутствует, но содержит undefined, значение
по умолчанию также применяется:
schema.validate({ role: undefined });
// результат: { role: 'user' }
Если же поле содержит валидное значение, оно сохраняется без изменений:
schema.validate({ role: 'admin' });
// результат: { role: 'admin' }
Важное поведение Joi связано с обработкой null. По
умолчанию null не заменяется значением default, если явно
не указано обратное.
const schema = Joi.object({
role: Joi.string().default('user')
});
schema.validate({ role: null });
// результат: { role: null }
Для того чтобы null также заменялся значением по
умолчанию, используется комбинация .default() и
.allow(null) с дополнительной логикой:
const schema = Joi.object({
role: Joi.string().allow(null).default('user')
});
Однако в таком виде null всё равно может пройти как
допустимое значение. Для принудительной замены используется
предобработка через empty() или кастомная логика.
Joi поддерживает использование функций в качестве значения default. Это позволяет вычислять значение динамически при каждой валидации.
const schema = Joi.object({
createdAt: Joi.date().default(() => new Date(), 'current date')
});
Каждый вызов validate() будет генерировать новое
значение даты, а не переиспользовать одно и то же.
Функция может принимать объект контекста:
const schema = Joi.object({
id: Joi.string(),
ref: Joi.string().default((value, helpers) => {
return `ref-${helpers.state.ancestors[0].id}`;
})
});
Здесь используется доступ к уже валидированным данным через
helpers.state.
Default может зависеть от других полей схемы. Это достигается через
ссылки (Joi.ref) или через функции.
const schema = Joi.object({
currency: Joi.string().default('USD'),
price: Joi.number(),
priceWithTax: Joi.number().default((value, helpers) => {
const { price, currency } = helpers.state.ancestors[0];
return currency === 'USD' ? price * 1.2 : price;
})
});
Такой подход позволяет строить производные значения прямо на этапе валидации.
Методы default() и required() не
конфликтуют напрямую, но влияют на итоговую обязательность значения.
const schema = Joi.object({
role: Joi.string().default('user').required()
});
Если поле отсутствует, default подставится, и требование
required() будет выполнено автоматически.
Однако если значение невозможно вывести (например, default — функция, возвращающая undefined), поведение может привести к ошибке валидации.
Default может задавать сложные структуры, включая объекты и массивы.
const schema = Joi.object({
settings: Joi.object({
theme: Joi.string().default('dark'),
notifications: Joi.boolean().default(true)
}).default()
});
Если settings отсутствует, он будет создан целиком с
вложенными значениями.
Для массивов:
const schema = Joi.object({
tags: Joi.array().items(Joi.string()).default([])
});
Важно учитывать, что массив и объект должны создаваться заново при каждой валидации, иначе возможны проблемы с мутацией общего объекта.
Для сложных сценариев используется Joi.lazy(),
позволяющий откладывать вычисление схемы и значений.
const schema = Joi.object({
config: Joi.lazy(() =>
Joi.object({
version: Joi.number().default(1)
})
)
});
Это полезно при рекурсивных структурах или динамической генерации схем.
Joi применяет строгий порядок обработки значений:
undefineddefault()Это означает, что default всегда применяется до финальной проверки ограничений типа.
Joi может сначала подставить default, а затем преобразовать значение:
const schema = Joi.object({
count: Joi.number().default('10')
});
schema.validate({});
// результат: { count: 10 }
Строка '10' сначала подставляется, затем приводится к
числу.
В некоторых случаях требуется запретить автоматическую подстановку
значений. Это делается через .presence('required') на
уровне объекта или через явное управление схемой.
const schema = Joi.object({
role: Joi.string().required()
});
Здесь отсутствие значения приведёт к ошибке, даже если default мог бы быть задан.
После валидации Joi возвращает уже преобразованный объект, где default значения становятся частью результата. Это важно учитывать при дальнейшей передаче данных:
const result = schema.validate({});
console.log(result.value);
// содержит все default-поля
Если используется опция stripUnknown, она не влияет на
default, но может удалить лишние входные поля до применения схемы.
Часто встречается проблема повторного использования объектов:
const defaultSettings = {
theme: 'dark'
};
const schema = Joi.object({
settings: Joi.object().default(defaultSettings)
});
Такой подход может привести к общей ссылке на объект. Более безопасный вариант:
Joi.object().default(() => ({ theme: 'dark' }))
Это гарантирует создание нового объекта при каждой валидации.
Вложенные default применяются рекурсивно, но только в пределах явно определённых схем:
const schema = Joi.object({
user: Joi.object({
profile: Joi.object({
age: Joi.number().default(18)
})
})
});
Если user или profile отсутствуют, default
для age не сработает, пока не будут определены
промежуточные объекты. Для этого требуется задавать default на каждом
уровне вложенности.