Утилиты типов

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

Основные функции работы со структурами

create — выполняет валидацию и возвращает значение, приведённое к описанной структуре. В случае ошибки выбрасывает исключение.

import { create, string } from 'superstruct';

const Name = string();

const value = create('Alex', Name); // 'Alex'

assert — аналогична create, но используется, когда требуется явная проверка с выбросом исключения без возврата значения.

import { assert, number } from 'superstruct';

assert(42, number()); // проходит
assert('42', number()); // ошибка

is — возвращает булево значение, определяющее соответствие структуры.

import { is, boolean } from 'superstruct';

is(true, boolean()); // true
is(1, boolean());   // false

validate — предоставляет расширенный результат проверки: валидность и преобразованное значение или ошибку.

import { validate, string } from 'superstruct';

const [error, value] = validate(123, string());

Маскирование и приведение значений

mask — приводит объект к структуре, заполняя недостающие поля undefined или значениями по умолчанию, если это возможно.

import { mask, object, string, number } from 'superstruct';

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

mask({ name: 'Alex' }, User);
// { name: 'Alex', age: undefined }

Инференс типов TypeScript

Superstruct предоставляет утилиту Infer, позволяющую извлекать TypeScript-тип из структуры.

import { Infer, string } from 'superstruct';

const Name = string();

type NameType = Infer<typeof Name>;

Это обеспечивает синхронизацию между runtime-валидацией и статической типизацией.


Утилиты модификации структур

optional

Позволяет сделать поле необязательным. Если значение отсутствует, оно считается валидным.

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

const User = object({
  nickname: optional(string())
});

nullable

Разрешает значение null как допустимое.

import { nullable, number } from 'superstruct';

const Age = nullable(number());

defaulted

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

import { defaulted, string } from 'superstruct';

const Name = defaulted(string(), 'Anonymous');

Важная особенность: значение по умолчанию применяется только при undefined, но не при null.

coerce

Позволяет преобразовать входные данные перед валидацией. Используется для нормализации типов.

import { coerce, number, string } from 'superstruct';

const Age = coerce(number(), string(), (value) => Number(value));

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

refine

Добавляет пользовательскую проверку поверх базовой структуры.

import { refine, number } from 'superstruct';

const Positive = refine(number(), 'Positive', (value) => {
  return value > 0;
});

Refine не изменяет тип, а только накладывает дополнительное ограничение.

define

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

import { define } from 'superstruct';

const EvenNumber = define('EvenNumber', (value) => {
  return typeof value === 'number' && value % 2 === 0;
});

Утилиты композиции структур

object

Создаёт структурированный объект с заданной схемой полей.

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

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

array

Описывает массив элементов одного типа.

import { array, number } from 'superstruct';

const Numbers = array(number());

tuple

Фиксированная структура массива с разными типами по позициям.

import { tuple, string, number } from 'superstruct';

const Pair = tuple([string(), number()]);

record

Используется для объектов с динамическими ключами, где все значения имеют одинаковую структуру.

import { record, number } from 'superstruct';

const Scores = record(string(), number());

union

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

import { union, string, number } from 'superstruct';

const StringOrNumber = union([string(), number()]);

intersection

Требует соответствия сразу нескольким структурам.

import { intersection, object, string } from 'superstruct';

const A = object({ name: string() });
const B = object({ surname: string() });

const Full = intersection([A, B]);

literal

Фиксированное значение.

import { literal } from 'superstruct';

const Yes = literal('yes');

enums

Ограничение значений набором допустимых вариантов.

import { enums } from 'superstruct';

const Role = enums(['admin', 'user', 'guest']);

Утилиты ограничения значений

size

Ограничивает длину строк или массивов.

import { size, string } from 'superstruct';

const Code = size(string(), 5, 10);

pattern

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

import { pattern, string } from 'superstruct';

const Email = pattern(string(), /^[^\s@]+@[^\s@]+\.[^\s@]+$/);

trimmed

Удаляет пробелы и проверяет уже очищенное значение.

import { trimmed, string } from 'superstruct';

const Name = trimmed(string());

Композиция и порядок применения утилит

Утилиты Superstruct не являются изолированными. Они проектировались как слои, накладываемые друг на друга. Порядок применения влияет на результат валидации и трансформации.

Пример комбинированной структуры:

import { object, string, defaulted, trimmed, size } from 'superstruct';

const User = object({
  name: size(trimmed(string()), 2, 30),
  nickname: defaulted(string(), 'guest')
});

Здесь данные проходят цепочку:

  1. Преобразование (trimmed)
  2. Ограничение (size)
  3. Применение дефолта (defaulted)
  4. Проверка объекта (object)

Типизация и связь с runtime-валидацией

Superstruct обеспечивает тесную связь между описанием структуры и TypeScript-типами. Утилита Infer позволяет автоматически синхронизировать типы.

import { object, string, number, Infer } from 'superstruct';

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

type UserType = Infer<typeof User>;

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


Поведение утилит в цепочках преобразования

При сложных композициях важно учитывать:

  • coerce выполняется до основной валидации
  • defaulted срабатывает при отсутствии значения
  • refine применяется после базовой проверки
  • mask не выбрасывает ошибки, а нормализует структуру

Пример последовательной обработки:

import { coerce, defaulted, refine, number } from 'superstruct';

const Age = refine(
  defaulted(
    coerce(number(), string(), Number),
    18
  ),
  'Age',
  (v) => v >= 0
);

Особенности проектирования схем

При использовании утилит важно учитывать, что каждая структура является функцией проверки, а не просто описанием данных. Это означает:

  • все трансформации происходят в момент выполнения
  • порядок утилит влияет на результат
  • композиция создаёт новые структуры без мутаций исходных

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