В Superstruct схема (struct) выступает как декларативное описание формы данных, определяющее допустимую структуру, типы и правила валидации значений. Любая схема здесь рассматривается как функция или композиция функций, которая принимает входное значение и либо приводит его к корректному виду, либо сообщает о нарушении ограничений.
Основу системы составляют примитивные структуры, описывающие одиночные значения.
Простейшие схемы определяют базовые типы данных:
import { string, number, boolean } from 'superstruct'
string()
number()
boolean()
Каждая из этих структур проверяет соответствие значения ожидаемому типу:
string() — допускает только строкиnumber() — допускает только числа (исключая NaN в
типичных конфигурациях)boolean() — допускает true/falseПри попытке передачи несовместимого значения формируется ошибка валидации, содержащая информацию о пути и причине несоответствия.
Объектные схемы позволяют описывать сложные структуры данных, состоящие из набора полей.
import { object, string, number } from 'superstruct'
const User = object({
name: string(),
age: number(),
})
Такая структура фиксирует контракт данных:
name и ageПри проверке объекта валидатор проходит по каждому ключу и применяет соответствующую подструктуру.
Объекты могут включать другие объекты, формируя иерархические схемы:
const Profile = object({
username: string(),
stats: object({
posts: number(),
likes: number(),
}),
})
Каждый уровень проверяется независимо, что позволяет локализовать ошибки внутри сложных структур.
Для описания повторяющихся элементов используется структура массива.
import { array, string } from 'superstruct'
const Tags = array(string())
Такое описание гарантирует:
Массивы могут комбинироваться с объектами:
const Users = array(
object({
id: number(),
name: string(),
})
)
Для ограниченных наборов значений применяются перечисления.
import { enums } from 'superstruct'
const Role = enums(['admin', 'user', 'guest'])
Такая схема фиксирует допустимое множество значений и отклоняет любые другие.
Литералы позволяют задать единственное допустимое значение:
import { literal } from 'superstruct'
const ActiveFlag = literal(true)
Это полезно для строгих контрактов конфигураций или маркеров состояния.
Union-структуры позволяют описывать значения, которые могут соответствовать одной из нескольких схем.
import { union, string, number } from 'superstruct'
const StringOrNumber = union([string(), number()])
При валидации значение проверяется последовательно по всем вариантам, пока не найдётся совпадение.
Такой подход используется для:
Реальные данные часто содержат отсутствие значений. Для этого используются специальные обёртки.
import { object, string, optional } from 'superstruct'
const Schema = object({
nickname: optional(string()),
})
Поле может отсутствовать, но если оно присутствует — обязано соответствовать типу.
import { nullable, number } from 'superstruct'
const MaybeNumber = nullable(number())
Допускается null либо корректное значение указанного
типа.
Для автоматического заполнения отсутствующих полей применяется механизм дефолтов.
import { defaulted, string } from 'superstruct'
const Name = defaulted(string(), 'unknown')
Если значение отсутствует, оно заменяется на заданное.
В объектных схемах это позволяет формировать полные структуры даже при неполных входных данных.
Некоторые схемы поддерживают приведение типов перед проверкой.
import { coerce, number, string } from 'superstruct'
const CoercedNumber = coerce(number(), string(), value => Number(value))
В этом случае строковое значение сначала преобразуется, а затем проходит стандартную валидацию.
Подобный механизм используется для:
Базовые типы часто недостаточны для описания бизнес-логики. Для этого применяется уточнение условий.
import { refine, string } from 'superstruct'
const NonEmptyString = refine(string(), 'nonEmpty', value => value.length > 0)
Здесь добавляется дополнительное ограничение поверх базовой структуры.
Refinement позволяет выражать:
Каждая структура при проверке возвращает либо корректное значение, либо структурированную ошибку.
Ошибки содержат:
Это позволяет точно локализовать проблему внутри вложенных объектов.
Пример типичного поведения:
user.profile.age указывается полный
путьСхемы в Superstruct проектируются как композиционные элементы. Более сложные структуры строятся из простых без дублирования логики.
const Id = number()
const Name = string()
const User = object({
id: Id,
name: Name,
})
Композиция позволяет:
В некоторых случаях структура данных зависит от контекста или других значений. Для этого используются функции, возвращающие схемы:
const createSchema = (isAdmin) =>
object({
name: string(),
permissions: isAdmin ? array(string()) : literal(null),
})
Подобный подход используется для динамических API-ответов и конфигураций.
Superstruct не требует отдельного языка описания схем. Все структуры определяются прямо в коде, что обеспечивает:
Каждая схема фактически является исполняемой функцией валидации, что отличает подход от статических декларативных описаний.
Процесс валидации проходит поэтапно:
При использовании составных структур обход выполняется в глубину, что позволяет точно определять источник несоответствия даже в сложных объектах с несколькими уровнями вложенности.