Преобразование перед валидацией

Библиотека Yup поддерживает механизм преобразования значений до этапа проверки. Это один из важнейших аспектов работы схем, поскольку входящие данные редко приходят в идеально подготовленном виде. Пользовательские формы, HTTP-запросы, query-параметры, данные из CSV и JSON часто содержат строки вместо чисел, пробелы, пустые значения или нестандартные форматы.

Преобразование выполняется автоматически перед запуском валидаторов. Благодаря этому схема может не только проверять данные, но и нормализовывать их.


Механизм transform()

Основной инструмент преобразования — метод transform().

Сигнатура:

schema.transform((currentValue, originalValue) => {
  return transformedValue;
});

Аргументы:

  • currentValue — текущее значение после предыдущих преобразований
  • originalValue — исходное значение до любых трансформаций

Пример:

import * as yup from 'yup';

const schema = yup.string().transform((value) => {
  return value.trim();
});

schema.cast('   hello   ');
// "hello"

Метод cast() показывает результат преобразования без запуска валидации.


Последовательность обработки

Внутри Yup обработка происходит в следующем порядке:

  1. Получение исходного значения
  2. Выполнение transform()
  3. Приведение типов
  4. Проверка валидаторами (required, min, matches и др.)

Пример:

const schema = yup
  .string()
  .transform((value) => value.trim())
  .min(3);

await schema.validate('  abc  ');

Этапы:

"  abc  "
↓
"abc"
↓
min(3)
↓
успешная проверка

transform() не изменяет исходный объект

Yup никогда не мутирует оригинальные данные.

const user = {
  name: '   Alex   '
};

const schema = yup.object({
  name: yup.string().transform(v => v.trim())
});

const result = schema.cast(user);

console.log(user.name);
// "   Alex   "

console.log(result.name);
// "Alex"

Разница между cast() и validate()

cast()

Только преобразует данные.

schema.cast(value);

validate()

Преобразует и валидирует.

await schema.validate(value);

Пример:

const schema = yup.number();

schema.cast('42');
// 42

await schema.validate('42');
// 42

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

Yup умеет автоматически преобразовывать типы.

String → Number

const schema = yup.number();

schema.cast('100');
// 100

String → Boolean

const schema = yup.boolean();

schema.cast('true');
// true

String → Date

const schema = yup.date();

schema.cast('2025-01-01');

Отключение преобразований через strict()

По умолчанию Yup приводит типы автоматически. Метод strict() отключает это поведение.

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

await schema.validate('42');

Ошибка:

this must be a `number` type

Без strict() строка была бы преобразована в число.


Очистка строк

trim()

Удаляет пробелы по краям.

const schema = yup.string().trim();

schema.cast('  hello  ');
// "hello"

lowercase()

Преобразует строку в нижний регистр.

const schema = yup.string().lowercase();

schema.cast('HELLO');
// "hello"

uppercase()

const schema = yup.string().uppercase();

schema.cast('hello');
// "HELLO"

Комбинирование преобразований

Трансформации можно объединять.

const schema = yup
  .string()
  .trim()
  .lowercase()
  .transform(v => v.replace(/\s+/g, '-'));

schema.cast('   HELLO   WORLD   ');
// "hello-world"

Работа с пустыми строками

Очень распространённая задача — преобразование пустой строки в null.

const schema = yup
  .string()
  .nullable()
  .transform((value) => {
    return value === '' ? null : value;
  });

schema.cast('');
// null

Особенно полезно для HTML-форм.


nullable() и трансформации

Метод nullable() разрешает null как валидное значение.

const schema = yup
  .number()
  .nullable();

Без nullable() значение null вызовет ошибку.


Преобразование чисел

Удаление лишних символов

const schema = yup.number().transform((value, originalValue) => {
  if (typeof originalValue === 'string') {
    return Number(originalValue.replace(/\s/g, ''));
  }

  return value;
});

schema.cast('1 000');
// 1000

Преобразование валюты

const schema = yup.number().transform((value, originalValue) => {
  return Number(
    originalValue
      .replace('$', '')
      .replace(',', '')
  );
});

schema.cast('$1,500');
// 1500

Использование originalValue

originalValue особенно важен, когда Yup уже выполнил автоматическое преобразование.

Пример:

const schema = yup.number().transform((value, originalValue) => {
  console.log(value);
  console.log(originalValue);

  return value;
});

Для строки "42":

value = 42
originalValue = "42"

Преобразование дат

Поддержка нестандартных форматов

const schema = yup.date().transform((value, originalValue) => {
  const parts = originalValue.split('.');

  return new Date(
    parts[2],
    parts[1] - 1,
    parts[0]
  );
});

schema.cast('25.12.2025');

Защита от invalid date

const schema = yup.date().transform((value, originalValue) => {
  const date = new Date(originalValue);

  return isNaN(date.getTime())
    ? null
    : date;
});

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

Преобразование элементов

const schema = yup.array().of(
  yup.string().trim().lowercase()
);

schema.cast([
  '  JS  ',
  '  NODE  '
]);

// ["js", "node"]

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

const schema = yup.array().transform((value, originalValue) => {
  if (typeof originalValue === 'string') {
    return originalValue.split(',');
  }

  return value;
});

schema.cast('a,b,c');
// ["a", "b", "c"]

Преобразование объектов

Очистка полей

const schema = yup.object({
  name: yup.string().trim(),
  email: yup.string().lowercase().trim()
});

schema.cast({
  name: '  Alex  ',
  email: '  TEST@MAIL.COM '
});

Результат:

{
  name: 'Alex',
  email: 'test@mail.com'
}

stripUnknown()

Метод удаляет поля, которых нет в схеме.

const schema = yup.object({
  name: yup.string()
});

schema.cast(
  {
    name: 'Alex',
    role: 'admin'
  },
  {
    stripUnknown: true
  }
);

Результат:

{
  name: 'Alex'
}

Преобразование undefined

Установка значений по умолчанию

const schema = yup.string().default('Anonymous');

schema.cast(undefined);
// "Anonymous"

default() и transform()

transform() выполняется раньше default().

const schema = yup
  .string()
  .transform(v => v === '' ? undefined : v)
  .default('Empty');

schema.cast('');
// "Empty"

Комплексная нормализация формы

Пример подготовки формы регистрации:

const schema = yup.object({
  name: yup
    .string()
    .trim(),

  email: yup
    .string()
    .trim()
    .lowercase(),

  age: yup
    .number()
    .transform((value, originalValue) => {
      return originalValue === ''
        ? null
        : value;
    })
    .nullable(),

  tags: yup
    .array()
    .transform((value, originalValue) => {
      if (typeof originalValue === 'string') {
        return originalValue
          .split(',')
          .map(v => v.trim());
      }

      return value;
    })
});

Входные данные:

{
  name: '  Alex  ',
  email: '  TEST@MAIL.COM ',
  age: '',
  tags: 'js, node, react'
}

После cast():

{
  name: 'Alex',
  email: 'test@mail.com',
  age: null,
  tags: ['js', 'node', 'react']
}

transform() и асинхронность

Трансформации всегда синхронные.

Нельзя:

.transform(async value => {
  return await fetchSomething(value);
})

transform() должен возвращать значение сразу.


Когда transform() не вызывается

Если схема работает в strict-режиме:

const schema = yup
  .string()
  .strict();

Некоторые встроенные преобразования отключаются, но пользовательские transform() продолжают работать.


Использование isType()

Метод проверяет, соответствует ли значение типу схемы.

const schema = yup.number();

schema.isType(42);
// true

schema.isType('42');
// false

Полезно внутри трансформаций:

const schema = yup.number().transform((value, originalValue) => {
  if (schema.isType(value)) {
    return value;
  }

  return Number(originalValue);
});

Обработка NaN

Преобразование числа может вернуть NaN.

const schema = yup.number();

schema.cast('abc');
// NaN

Без дополнительной обработки это приведёт к ошибке валидации.

Безопасный вариант:

const schema = yup.number().transform((value) => {
  return isNaN(value)
    ? undefined
    : value;
});

Контекст преобразования

Внутри transform() доступен контекст схемы через this.

const schema = yup.string().transform(function(value) {
  console.log(this.path);

  return value;
});

Важно использовать обычную функцию, а не стрелочную.


transform() в цепочках схем

Трансформации выполняются последовательно.

const schema = yup
  .string()
  .transform(v => v.trim())
  .transform(v => v.toUpperCase());

schema.cast('  hello  ');
// "HELLO"

Производительность трансформаций

Трансформации вызываются при каждом:

  • cast()
  • validate()
  • validateSync()

Сложные вычисления внутри transform() могут влиять на производительность при обработке больших форм.

Нежелательно:

.transform(value => {
  return expensiveOperation(value);
})

Типичный pipeline обработки данных

Практический сценарий:

const schema = yup.object({
  username: yup
    .string()
    .trim()
    .lowercase(),

  phone: yup
    .string()
    .transform(v => v.replace(/\D/g, '')),

  salary: yup
    .number()
    .transform((v, original) => {
      return Number(
        original.replace(/[^\d]/g, '')
      );
    })
});

Результат:

{
  username: 'alex',
  phone: '79991234567',
  salary: 250000
}

Частые ошибки

Ошибка: отсутствие return

.transform(value => {
  value.trim();
})

Результат:

undefined

Правильно:

.transform(value => {
  return value.trim();
})

Ошибка: работа с null

.transform(value => value.trim())

При null возникнет ошибка.

Безопасный вариант:

.transform(value => {
  return typeof value === 'string'
    ? value.trim()
    : value;
})

Ошибка: изменение объекта напрямую

Неправильно:

.transform(obj => {
  obj.name = obj.name.trim();

  return obj;
})

Лучше:

.transform(obj => ({
  ...obj,
  name: obj.name.trim()
}))

Практика нормализации API-данных

const apiSchema = yup.object({
  id: yup.number(),

  status: yup
    .string()
    .trim()
    .uppercase(),

  active: yup.boolean(),

  createdAt: yup.date(),

  tags: yup.array().of(
    yup.string().trim().lowercase()
  )
});

Вход:

{
  id: '15',
  status: ' active ',
  active: 'true',
  createdAt: '2025-01-01',
  tags: [' JS ', ' NODE ']
}

Результат:

{
  id: 15,
  status: 'ACTIVE',
  active: true,
  createdAt: Date,
  tags: ['js', 'node']
}

Архитектурная роль преобразований

В Yup трансформации выполняют несколько задач одновременно:

  • нормализация пользовательского ввода;
  • приведение типов;
  • очистка данных;
  • подготовка к валидации;
  • унификация форматов;
  • удаление лишних символов;
  • конвертация пустых значений;
  • подготовка API-данных.

Благодаря этому схема становится не только инструментом проверки, но и полноценным уровнем предобработки данных.