Валидация вложенных объектов

Библиотека 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)

Функция 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-типы

Для динамических структур используется 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() используется для отложенного определения схемы, предотвращая циклические зависимости.


Ошибки и их агрегация во вложенных структурах

При глубокой валидации ошибки агрегируются в единый объект, где ключи отражают путь:

  • user
  • user.profile
  • user.profile.contacts.email

Такая модель упрощает интеграцию с формами и UI-библиотеками, позволяя напрямую привязывать ошибки к полям.


Совместная работа с TypeScript

При использовании 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() влияет только на текущий узел
  • отсутствие объекта не активирует проверки его внутренних полей

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