Валидация данных в JavaScript-экосистеме часто сталкивается с
проблемой несоответствия форматов ключей между слоями приложения.
Серверные API нередко используют snake_case, тогда как
клиентский код и большинство современных JavaScript-проектов
придерживаются camelCase.
Библиотека Yup ориентирована на валидацию значений, а не на преобразование структуры объекта, однако механизм трансформаций позволяет внедрять преобразование ключей на уровне схемы.
Ключевая особенность: Yup не предоставляет встроенных методов
camelCase или snake_case для ключей, поэтому
преобразование реализуется через transform, кастомные
методы и предварительную нормализацию данных.
Различие форматов ключей возникает на стыке систем:
user_name, created_atuserName,
createdAtsnake_casecamelCaseНесогласованность приводит к необходимости унификации данных до этапа валидации или внутри неё.
Наиболее стабильный и предсказуемый подход — преобразование объекта до передачи в Yup.
Используются утилиты:
camelcase-keyssnakecase-keysПример логики:
import camelcaseKeys from "camelcase-keys";
const normalizedData = camelcaseKeys(inputData, { deep: true });
schema.validateSync(normalizedData);
Особенности подхода:
Yup предоставляет метод transform, который применяется
до проверки значений.
Базовый принцип: преобразование входного объекта целиком перед началом валидации.
import * as yup from "yup";
import camelcaseKeys from "camelcase-keys";
const schema = yup.object({
userName: yup.string(),
createdAt: yup.date()
}).transform((value, originalValue) => {
return camelcaseKeys(originalValue, { deep: true });
});
В этом случае:
При отсутствии внешних библиотек применяется ручная рекурсия.
const toCamelCase = (str) =>
str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
const camelizeObject = (obj) => {
if (Array.isArray(obj)) {
return obj.map(camelizeObject);
}
if (obj !== null && typeof obj === "object") {
return Object.keys(obj).reduce((acc, key) => {
const newKey = toCamelCase(key);
acc[newKey] = camelizeObject(obj[key]);
return acc;
}, {});
}
return obj;
};
Использование в Yup:
const schema = yup.object().transform((value, originalValue) => {
return camelizeObject(originalValue);
});
Обратное преобразование используется при отправке данных на сервер.
const toSnakeCase = (str) =>
str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
const snakeizeObject = (obj) => {
if (Array.isArray(obj)) {
return obj.map(snakeizeObject);
}
if (obj !== null && typeof obj === "object") {
return Object.keys(obj).reduce((acc, key) => {
const newKey = toSnakeCase(key);
acc[newKey] = snakeizeObject(obj[key]);
return acc;
}, {});
}
return obj;
};
Метод transform имеет два ключевых параметра:
value — текущее значение после предыдущих
трансформацийoriginalValue — исходные данные до валидацииВажно учитывать:
transform выполняется до caststripUnknown аккуратноПри глубокой структуре данных преобразование должно быть рекурсивным.
Пример структуры:
{
user_profile: {
first_name: "John",
last_name: "Doe"
}
}
После camelCase:
{
userProfile: {
firstName: "John",
lastName: "Doe"
}
}
Yup-схема:
const schema = yup.object({
userProfile: yup.object({
firstName: yup.string(),
lastName: yup.string()
})
}).transform((value, originalValue) => camelizeObject(originalValue));
Массивы требуют отдельной обработки, так как содержат вложенные структуры.
const schema = yup.object({
users: yup.array().of(
yup.object({
userName: yup.string(),
createdAt: yup.date()
})
)
}).transform((value, originalValue) => camelizeObject(originalValue));
Рекурсивная функция должна учитывать:
Для переиспользования логики преобразования создаются расширения
через addMethod.
import * as yup from "yup";
yup.addMethod(yup.object, "camelizeKeys", function () {
return this.transform((value, originalValue) => {
return camelizeObject(originalValue);
});
});
const schema = yup.object().camelizeKeys();
Такой подход позволяет:
После преобразования ключей структура может не совпасть с определением схемы Yup, если схема написана под исходный формат.
Повторное применение transform или внешней нормализации
приводит к некорректным ключам:
user_name → userName →
userName (повторное преобразование может исказить
данные)При включённом stripUnknown: true могут удаляться ключи,
которые были корректно преобразованы, но отсутствуют в схеме.
Преобразование ключей на уровне Yup увеличивает нагрузку на валидацию:
Оптимизация:
| Подход | Преимущества | Недостатки |
|---|---|---|
| Pre-transform (до Yup) | высокая предсказуемость | дополнительный слой обработки |
| transform в Yup | централизованная логика | сложнее отладка |
| рекурсивная функция | гибкость | риск ошибок и нагрузка |
| кастомные методы | переиспользование | усложнение архитектуры |
При использовании TypeScript изменение ключей влияет на типизацию:
zod-to-ts или ручные
интерфейсыYup не синхронизирует типы с трансформацией, что требует явного контроля структуры данных на уровне кода.