Преобразование типов в Joi является одной из ключевых возможностей, позволяющих не только проверять входные данные, но и приводить их к ожидаемому формату ещё до передачи в бизнес-логику приложения. Это особенно важно в серверных приложениях на Node.js, где данные часто приходят из HTTP-запросов в виде строк, даже если по смыслу они должны быть числами, булевыми значениями или структурированными объектами.
Joi по умолчанию работает в режиме, при котором входные значения проходят через этап преобразования (casting) перед валидацией. Это означает, что библиотека пытается привести данные к типу, указанному в схеме, прежде чем применять правила проверки.
Например, строка "42" может быть автоматически
преобразована в число 42, если схема ожидает тип
number.
Базовый принцип:
При этом поведение преобразования можно контролировать как глобально, так и локально для каждой схемы.
convertКлючевая настройка, отвечающая за преобразование типов, —
convert.
schema.validate(value, { convert: true })
По умолчанию convert: true, то есть Joi активно пытается
привести данные к нужному типу.
Пример:
import Joi from 'joi';
const schema = Joi.object({
age: Joi.number().integer().min(0)
});
const result = schema.validate({ age: "25" });
console.log(result.value);
Результат:
{ age: 25 }
Здесь строка "25" была автоматически преобразована в
число 25.
Если отключить преобразование:
const result = schema.validate({ age: "25" }, { convert: false });
console.log(result.value);
Результат:
{ age: "25" }
Теперь значение остаётся строкой, и валидация может завершиться ошибкой.
Joi активно преобразует строки в числа:
const schema = Joi.object({
price: Joi.number()
});
schema.validate({ price: "19.99" });
Результат:
{ price: 19.99 }
Однако преобразование имеет ограничения:
"10abc" → ошибка"" → может стать NaN или вызвать ошибку в
зависимости от правилnull → не преобразуется автоматически в 0Преобразование в boolean особенно важно, поскольку HTTP часто передаёт значения как строки:
const schema = Joi.object({
isActive: Joi.boolean()
});
Примеры преобразований:
"true" → true"false" → false"1" → true"0" → falseschema.validate({ isActive: "1" });
// { isActive: true }
Важно учитывать, что не все строки интерпретируются как boolean.
Например, "yes" или "no" не всегда
преобразуются автоматически.
Строки в Joi обычно не требуют преобразования, но библиотека может приводить другие типы к строкам:
const schema = Joi.object({
id: Joi.string()
});
schema.validate({ id: 123 });
Результат:
{ id: "123" }
Это поведение может быть полезным при работе с идентификаторами, приходящими из базы данных или URL.
Joi предоставляет ряд методов, которые явно управляют трансформацией данных.
number(),
string(), boolean() как
кастинг-инструментыКаждый базовый тип уже включает встроенное преобразование:
Joi.number()
Joi.string()
Joi.boolean()
Они не только валидируют, но и приводят тип.
default()Метод default() не является прямым преобразованием типа,
но участвует в нормализации данных:
const schema = Joi.object({
role: Joi.string().default('user')
});
schema.validate({});
Результат:
{ role: "user" }
custom() для
ручного преобразованияКогда встроенных возможностей недостаточно, используется кастомная логика:
const schema = Joi.object({
value: Joi.string().custom((value, helpers) => {
return value.trim().toLowerCase();
})
});
Пример:
schema.validate({ value: " HELLO " });
Результат:
{ value: "hello" }
Строковые методы часто используются как часть преобразования данных:
Joi.string().trim().lowercase().uppercase()
Хотя lowercase() и uppercase() взаимно
исключают смысловое использование, они демонстрируют идею цепочки
трансформаций.
Пример:
const schema = Joi.object({
username: Joi.string().trim().lowercase()
});
schema.validate({ username: " JohnDoe " });
Результат:
{ username: "johndoe" }
Joi может нормализовать структуру объектов, удаляя лишние поля:
const schema = Joi.object({
name: Joi.string()
}).unknown(false);
Вход:
{ name: "Alex", age: 30 }
Результат:
{ name: "Alex" }
Здесь происходит не только валидация, но и структурное преобразование.
Массивы также подвергаются преобразованию элементов:
const schema = Joi.array().items(Joi.number());
Вход:
["1", "2", "3"]
Результат:
[1, 2, 3]
Каждый элемент преобразуется индивидуально.
Одним из сильных аспектов Joi является способность рекурсивно применять преобразования:
const schema = Joi.object({
user: Joi.object({
age: Joi.number(),
active: Joi.boolean()
})
});
Вход:
{
user: {
age: "20",
active: "true"
}
}
Результат:
{
user: {
age: 20,
active: true
}
}
Преобразование выполняется на каждом уровне вложенности.
Важно понимать порядок операций:
default()min, max,
pattern)Это влияет на поведение сложных схем. Например:
const schema = Joi.number().min(10);
schema.validate("15");
Сначала "15" превращается в 15, затем
проверяется условие min(10).
Можно полностью запретить преобразование:
const schema = Joi.number().strict();
Теперь:
schema.validate("42");
Результат — ошибка, так как строка не будет преобразована.
Для сложных приложений удобно задавать единое поведение:
const schema = Joi.object({
id: Joi.number()
}).prefs({ convert: false });
Теперь вся схема работает без автоматического кастинга.
Преобразование типов валидационной библиотекой решает несколько прикладных задач:
parseInt,
JSON.parseПри этом чрезмерное доверие автоматическому кастингу может привести к скрытым ошибкам, когда данные выглядят корректно, но были преобразованы неожиданным образом.
Неожиданное приведение пустых строк
Joi.number().validate("")
Может привести к NaN или ошибке.
Потеря точности при преобразовании
Скрытые преобразования boolean
"false" может интерпретироваться неожиданно в некоторых
конфигурацияхАвтоматическое удаление полей
.unknown(false)В сложных системах часто комбинируют подходы:
convert: true на уровне API-слояstrict() на уровне доменной логики.custom() для нестандартных случаевЭто позволяет разделить зоны ответственности:
Такой подход делает поведение схем более предсказуемым и снижает риск скрытых ошибок в данных.