Библиотека Yup предоставляет декларативный механизм описания схем валидации, в котором сложные структуры данных моделируются через композицию базовых схем. Вложенные объекты являются одной из ключевых возможностей, позволяющих описывать реальные доменные модели с глубокой структурой данных: профили пользователей, формы с адресами, конфигурации, вложенные DTO и API-ответы.
Основной принцип заключается в том, что каждый уровень объекта
описывается отдельной схемой, а затем объединяется через
object().shape().
Вложенная валидация начинается с описания объекта через
yup.object() и передачи структуры через
shape.
import * as yup from 'yup';
const schema = yup.object({
user: yup.object({
name: yup.string().required(),
age: yup.number().min(18)
})
});
Здесь поле user является объектом, внутри которого
определены собственные правила валидации.
Особенность подхода заключается в том, что Yup рекурсивно применяет схему ко всем уровням вложенности, сохраняя строгую типизацию правил на каждом узле дерева данных.
При увеличении сложности данных вложенность может становиться многоуровневой:
const schema = yup.object({
user: yup.object({
profile: yup.object({
firstName: yup.string().required(),
lastName: yup.string().required(),
contacts: yup.object({
email: yup.string().email().required(),
phone: yup.string()
})
})
})
});
Каждый уровень описывается независимо, но связан через структуру объекта. Это позволяет изолировать правила валидации и поддерживать их модульность.
Вложенный объект может быть опциональным. В таком случае используется
nullable() или отсутствие required():
const schema = yup.object({
user: yup.object({
profile: yup.object({
bio: yup.string()
}).nullable()
})
});
Если profile отсутствует или равен null,
валидация не будет считаться нарушенной.
При работе с API часто встречаются частично заполненные структуры. Для этого используются необязательные поля:
const schema = yup.object({
settings: yup.object({
theme: yup.string(),
notifications: yup.object({
email: yup.boolean(),
sms: yup.boolean()
})
})
});
Отсутствие полей не приводит к ошибке, если не указано
required().
Особый случай — комбинация массивов и объектов. Yup позволяет
описывать массивы через array().of():
const schema = yup.object({
users: yup.array().of(
yup.object({
id: yup.number().required(),
name: yup.string().required()
})
)
});
Каждый элемент массива валидируется по собственной схеме объекта.
При валидации вложенных структур Yup возвращает ошибки с путями в формате dot-notation:
{
"user.profile.contacts.email": "Invalid email"
}
Такая структура позволяет точно определить источник ошибки без дополнительного анализа данных.
Метод test() может применяться на любом уровне
вложенности:
const schema = yup.object({
user: yup.object({
password: yup.string().test(
'strong-password',
'Слабый пароль',
value => value && value.length > 8
)
})
});
Логика валидации может учитывать как локальное значение, так и контекст родительского объекта.
Yup поддерживает доступ к внешнему контексту через
context:
const schema = yup.object({
user: yup.object({
role: yup.string(),
accessLevel: yup.number().when('role', {
is: 'admin',
then: schema => schema.min(10),
otherwise: schema => schema.min(1)
})
})
});
Условная логика позволяет изменять правила валидации внутри вложенной структуры на основе соседних полей.
when() во вложенных объектахМетод when() часто применяется для динамической
валидации:
const schema = yup.object({
billing: yup.object({
country: yup.string(),
vatNumber: yup.string().when('country', {
is: 'EU',
then: schema => schema.required(),
otherwise: schema => schema.notRequired()
})
})
});
Зависимости могут быть как на уровне объекта, так и на уровне вложенного поля.
Функция reach() позволяет получить доступ к конкретному
вложенному узлу схемы:
const fieldSchema = yup.reach(schema, 'user.profile.contacts.email');
Это полезно при динамической валидации отдельных полей без запуска полной проверки объекта.
Перед валидацией данные могут быть преобразованы через
transform():
const schema = yup.object({
user: yup.object({
age: yup.number().transform(value => Number(value))
})
});
Трансформация применяется до выполнения проверки, включая вложенные уровни.
default() во вложенных объектахЗначения по умолчанию задаются на любом уровне структуры:
const schema = yup.object({
user: yup.object({
settings: yup.object({
theme: yup.string().default('light')
})
})
});
При отсутствии значения структура автоматически дополняется.
При работе с вложенными объектами часто требуется удаление лишних данных:
const schema = yup.object({
user: yup.object({
name: yup.string()
}).noUnknown(true)
});
Параметр noUnknown(true) удаляет все поля, не описанные
в схеме, включая вложенные.
Для динамических структур используется mixed():
const schema = yup.object({
data: yup.mixed().test(
'is-valid-structure',
'Неверная структура',
value => typeof value === 'object' && value !== null
)
});
Это позволяет валидировать неопределённые или изменяемые вложенные структуры.
Сложные структуры могут быть рекурсивными, например дерево:
const nodeSchema = yup.object({
name: yup.string().required(),
children: yup.array().of(
yup.lazy(() => nodeSchema)
)
});
lazy() используется для отложенного определения схемы,
предотвращая циклические зависимости.
При глубокой валидации ошибки агрегируются в единый объект, где ключи отражают путь:
useruser.profileuser.profile.contacts.emailТакая модель упрощает интеграцию с формами и UI-библиотеками, позволяя напрямую привязывать ошибки к полям.
При использовании TypeScript вложенные схемы позволяют автоматически выводить типы:
import * as yup from 'yup';
const schema = yup.object({
user: yup.object({
name: yup.string().required(),
age: yup.number()
})
});
type User = yup.InferType<typeof schema>;
Тип User будет содержать полностью структурированную
вложенную модель.
При обработке вложенных объектов важно учитывать несколько особенностей:
required() влияет только на текущий узелТакая модель обеспечивает предсказуемое поведение при работе с комплексными структурами данных.