Nested objects

Работа с вложенными структурами данных является одной из ключевых возможностей Superstruct при описании сложных схем валидации. В реальных приложениях данные редко представляют собой плоские объекты: чаще всего встречаются композиции, где один объект содержит другие объекты, массивы и их комбинации. Поддержка таких сценариев позволяет формировать строгие и выразительные модели данных без потери читаемости.

Базовый принцип вложенной валидации

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

В Superstruct используется функция object, позволяющая задавать структуру объекта, а значения полей могут быть как примитивами, так и другими структурами.

import { object, string, number } fr om "superstruct";

const User = object({
  name: string(),
  age: number(),
});

Даже в этом простом случае структура уже задаёт основу для расширения. Однако настоящая сила проявляется при вложении объектов.

Простое вложение объектов

Вложенные объекты описываются через использование object внутри другого object. Это позволяет формировать иерархические схемы.

import { object, string, number } from "superstruct";

const Address = object({
  city: string(),
  street: string(),
  zip: string(),
});

const User = object({
  name: string(),
  age: number(),
  address: Address,
});

Здесь поле address является полноценной структурой со своей собственной валидацией. При проверке объекта User сначала валидируется внешний уровень, затем рекурсивно проверяется вложенный объект.

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

Рекурсивная природа проверки

Superstruct применяет рекурсивную стратегию обхода структуры. Это означает, что при встрече вложенного объекта происходит переход на следующий уровень проверки.

Например, при валидации:

const data = {
  name: "Alex",
  age: 30,
  address: {
    city: "Almaty",
    street: "Abay Ave",
    zip: "050000",
  },
};

валидация происходит поэтапно:

  1. Проверка поля name как строки
  2. Проверка поля age как числа
  3. Переход к address
  4. Проверка структуры Address
  5. Проверка всех внутренних полей address

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

Комбинирование вложенных структур

Вложенные объекты редко существуют изолированно. Обычно они комбинируются с другими типами данных: массивами, объединениями и опциональными полями.

Вложенные объекты внутри массивов

import { object, string, array } from "superstruct";

const Post = object({
  title: string(),
  content: string(),
});

const Blog = object({
  posts: array(Post),
});

Здесь массив posts содержит объекты, каждый из которых должен соответствовать структуре Post.

Массивы внутри вложенных объектов

const User = object({
  name: string(),
  profile: object({
    tags: array(string()),
  }),
});

Такая структура демонстрирует гибкость вложенности: объект содержит объект, который содержит массив.

Многоуровневые иерархии

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

const Company = object({
  name: string(),
  location: object({
    country: string(),
    city: string(),
    office: object({
      floor: number(),
      room: string(),
    }),
  }),
});

Здесь наблюдается трёхуровневая иерархия:

  • Company

    • location

      • office

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

Повторное использование структур

Одним из важных преимуществ вложенных объектов является возможность переиспользования схем.

const Coordinates = object({
  lat: number(),
  lng: number(),
});

const Store = object({
  name: string(),
  location: Coordinates,
});

const Warehouse = object({
  id: string(),
  position: Coordinates,
});

Одна и та же структура Coordinates используется в разных контекстах без дублирования логики.

Расширение вложенных структур

В Superstruct можно расширять уже существующие структуры, добавляя новые поля на более высоком уровне вложенности.

Хотя библиотека не предоставляет классического наследования, композиция решает ту же задачу.

const BaseUser = object({
  name: string(),
  age: number(),
});

const AdminUser = object({
  user: BaseUser,
  permissions: array(string()),
});

В этом случае BaseUser используется как часть более сложной структуры AdminUser.

Опциональные и частичные вложенные поля

В реальных данных вложенные объекты часто бывают неполными. Для этого используются модификаторы, такие как optional.

import { object, string, optional } from "superstruct";

const Profile = object({
  bio: optional(string()),
  website: optional(string()),
});

const User = object({
  name: string(),
  profile: Profile,
});

Теперь объект profile может содержать не все поля, при этом структура остаётся валидируемой.

Валидация частично заполненных вложенных структур

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

const Settings = object({
  theme: optional(string()),
  notifications: object({
    email: optional(string()),
    sms: optional(string()),
  }),
});

Здесь допускается отсутствие любых внутренних значений, но структура остаётся фиксированной.

Глубокая композиция и читаемость

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

const Pagination = object({
  page: number(),
  lim it: number(),
});

const Meta = object({
  pagination: Pagination,
  total: number(),
});

const Response = object({
  data: array(string()),
  meta: Meta,
});

Такой подход делает сложные схемы модульными и легко расширяемыми.

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

При нарушении валидации Superstruct возвращает ошибку с указанием конкретного пути до проблемного поля. Это особенно важно для вложенных объектов.

Например, при ошибке в address.city путь ошибки будет явно указывать на уровень вложенности:

  • address

    • city

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

Вложенные структуры и преобразование данных

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

const RawUser = object({
  name: string(),
  location: object({
    city: string(),
    country: string(),
  }),
});

После валидации структура может использоваться как основа для трансформации данных в бизнес-логику приложения без дополнительных проверок.

Композиционный подход как основа архитектуры

Использование вложенных объектов в Superstruct формирует композиционный стиль описания данных. Вместо монолитных схем система разбивается на небольшие, переиспользуемые структуры.

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