Coerce и преобразования

Joi — система валидации схем, построенная вокруг идеи декларативного описания данных и их строгой проверки с возможностью автоматических преобразований типов. Одной из ключевых возможностей является механизм приведения (coercion), который позволяет нормализовать входные значения до этапа основной валидации.

Внутренний процесс обработки значения в Joi можно рассматривать как последовательность стадий:

  • нормализация входных данных (coercion)
  • применение дефолтов
  • основная валидация правил
  • постобработка (например, кастомные трансформации)

Coercion выполняется до проверки ограничений, что принципиально важно: схема работает не с тем, что пришло «как есть», а с тем, что было приведено к ожидаемому типу.

Типичный пример поведения:

const schema = Joi.number();

schema.validate("42"); // результат: 42 (число)

Строка "42" автоматически преобразуется в число, поскольку включена опция преобразования по умолчанию.

Опция convert и управление приведением

Поведение coercion регулируется глобально и локально через convert.

Глобальное отключение преобразований

const schema = Joi.number();

schema.validate("42", { convert: false });
// ошибка валидации: expected number

Или через параметры схемы:

const schema = Joi.object({
  value: Joi.number()
}).prefs({ convert: false });

Отключение coercion переводит Joi в строгий режим: тип входных данных должен совпадать с ожидаемым.

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

По умолчанию convert: true, что включает:

  • преобразование строк в числа
  • преобразование строк “true”/“false” в boolean (в некоторых сценариях)
  • нормализацию строк (trim, lowercase, uppercase — если указано)
  • приведение массивов и объектов в допустимые формы

Встроенные механизмы преобразований

Joi содержит набор встроенных трансформаций, которые являются частью coercion pipeline.

Приведение строк

const schema = Joi.string().trim().lowercase();

schema.validate("  HELLO  ");
// результат: "hello"

Этапы:

  1. trim удаляет пробелы
  2. lowercase приводит к нижнему регистру

Важно, что это не «валидаторы», а трансформеры.

Приведение чисел

const schema = Joi.number();

schema.validate("0032");
// 32

Поддерживается:

  • десятичные строки
  • экспоненциальная форма
  • частично числовые строки (если корректно парсятся)

Некорректные значения:

Joi.number().validate("12px"); // ошибка

Boolean coercion

const schema = Joi.boolean();

schema.validate("true");  // true
schema.validate("false"); // false
schema.validate(1);       // true
schema.validate(0);       // false

Coercion boolean значений опирается на набор предопределённых правил интерпретации.

Coercion и объекты схем

Автоматическое приведение структуры

const schema = Joi.object({
  id: Joi.number(),
  active: Joi.boolean()
});

schema.validate({
  id: "10",
  active: "true"
});

Результат:

{
  id: 10,
  active: true
}

Каждое поле проходит собственный pipeline преобразований.

stripUnknown и влияние на coercion

Joi.object({
  a: Joi.number()
}).prefs({ stripUnknown: true });

Coercion применяется до удаления неизвестных полей, что важно для корректной нормализации входа.

Кастомные преобразования через custom

Joi позволяет задавать пользовательскую логику трансформации.

const schema = Joi.string().custom((value, helpers) => {
  return value.replace(/\s+/g, "-");
});

schema.validate("hello world");
// "hello-world"

Особенности:

  • выполняется в рамках coercion-фазы
  • может возвращать изменённое значение
  • может генерировать ошибку через helpers.error

Использование alter для условных преобразований

const schema = Joi.object({
  name: Joi.string().alter({
    trim: (schema) => schema.trim(),
    upper: (schema) => schema.uppercase()
  })
});

const extended = schema.tailor('trim');

Механизм позволяет динамически включать разные наборы преобразований.

Extension API и низкоуровневый coercion

Для сложных типов используется расширение Joi:

const customType = Joi.extend((joi) => ({
  type: "timestamp",
  base: joi.number(),
  coerce(value) {
    if (typeof value === "string") {
      return { value: Date.parse(value) };
    }
    return { value };
  }
}));

Поведение coerce в extension:

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

Преобразования массивов

const schema = Joi.array().items(Joi.number());

schema.validate(["1", "2", "3"]);
// [1, 2, 3]

Дополнительные трансформации:

Joi.array().single();

Позволяет принимать одиночное значение как массив:

schema.validate("1"); // ["1"]

Default значения и их взаимодействие с coercion

const schema = Joi.number().default(10);

schema.validate(undefined);
// 10

Порядок:

  1. coercion входа
  2. если значение отсутствует — default
  3. дальнейшая валидация

presence и влияние на преобразования

Joi.object({
  a: Joi.number().required()
}).prefs({ presence: "optional" });

Coercion применяется даже к отсутствующим значениям при наличии default-логики, но required-поля должны присутствовать до финальной проверки.

Отключение преобразований для отдельных схем

const schema = Joi.number().strict();

schema.validate("42");
// ошибка

strict() эквивалентен convert: false на уровне схемы.

Влияние coercion на ref и вычисляемые поля

const schema = Joi.object({
  a: Joi.number(),
  b: Joi.number().valid(Joi.ref("a"))
});

Значение a сначала приводится к числу, и только затем используется в ref.

Приоритет преобразований

Порядок применения трансформаций:

  1. extension coerce
  2. встроенные coercion правила типа
  3. кастомные .custom()
  4. .default()
  5. финальная валидация

Практические особенности поведения

  • coercion всегда стремится привести данные к ожидаемому типу, а не отклонить их
  • строгий режим отключает все автоматические преобразования
  • порядок цепочки методов влияет на итоговый результат
  • преобразования не являются обратимыми

Документация и поведение API

Полное описание поведения и актуальные изменения схемной системы находятся в официальной документации библиотеки Joi Documentation