Работа с вложенными структурами данных является одной из ключевых возможностей 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",
},
};
валидация происходит поэтапно:
name как строкиage как числаaddressAddressaddressТакая модель позволяет сохранять предсказуемость при работе с глубоко вложенными структурами.
Вложенные объекты редко существуют изолированно. Обычно они комбинируются с другими типами данных: массивами, объединениями и опциональными полями.
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
Каждый уровень полностью изолирован с точки зрения валидации, но связан логически через структуру данных.
Одним из важных преимуществ вложенных объектов является возможность переиспользования схем.
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
Такая детализация позволяет точно локализовать проблему даже в сложных структурах.
В некоторых случаях вложенные объекты используются не только для проверки, но и для нормализации данных.
const RawUser = object({
name: string(),
location: object({
city: string(),
country: string(),
}),
});
После валидации структура может использоваться как основа для трансформации данных в бизнес-логику приложения без дополнительных проверок.
Использование вложенных объектов в Superstruct формирует композиционный стиль описания данных. Вместо монолитных схем система разбивается на небольшие, переиспользуемые структуры.
Такой подход снижает сложность поддержки и позволяет строить масштабируемые модели данных, где каждая часть отвечает только за свою область ответственности.