Базовая валидация массивов

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

Базовое определение массива

Для описания массива используется функция array, принимающая структуру элемента:

import { array, string } from 'superstruct'

const StringArray = array(string())

В этом случае валидным будет любой массив, состоящий исключительно из строк:

StringArray.assert(['a', 'b', 'c']) // ok
StringArray.assert(['a', 1])        // ошибка

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

Массивы примитивных типов

Superstruct предоставляет базовые структуры для примитивов: string, number, boolean. Их можно напрямую использовать внутри массива:

import { array, number } from 'superstruct'

const Numbers = array(number())

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

Массивы объектов

Часто массивы содержат не примитивы, а сложные структуры. В этом случае элементом массива становится struct:

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

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

const Users = array(User)

Каждый объект внутри массива проверяется независимо:

Users.assert([
  { id: 1, name: 'Alex' },
  { id: 2, name: 'Maria' }
])

Если хотя бы один объект нарушает структуру, весь массив считается невалидным.

Ограничение размера массива

Часто требуется контролировать длину массива. Для этого используется второй аргумент array, где задаются ограничения:

import { array, string } from 'superstruct'

const Tags = array(string(), {
  min: 1,
  max: 5
})

Здесь проверяется сразу два условия:

  • массив не может быть пустым
  • количество элементов не превышает 5

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

Проверка на непустой массив

Частный случай ограничения — требование наличия хотя бы одного элемента:

const NonEmptyStrings = array(string(), {
  min: 1
})

Это эквивалентно более строгой проверке, исключающей пустые коллекции, которые часто становятся источником логических ошибок в приложениях.

Вложенные массивы

Superstruct поддерживает произвольную глубину вложенности. Массив может содержать другие массивы:

const Matrix = array(array(number()))

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

Пример валидного значения:

Matrix.assert([
  [1, 2],
  [3, 4]
])

При этом несоответствие может возникнуть на любом уровне:

Matrix.assert([
  [1, 2],
  [3, 'x']
])

Ошибка будет зафиксирована в конкретном элементе вложенного массива.

Комбинация с union-типами

Массивы могут содержать элементы разных типов через union:

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

const Mixed = array(union([string(), number()]))

Теперь допустимы строки и числа одновременно:

Mixed.assert([1, 'a', 2, 'b'])

Такая конструкция используется при работе с неоднородными данными, например результатами API или логами.

Кастомные проверки элементов

Базовые структуры можно дополнять логикой через refine, создавая более строгие условия:

import { array, number, refine } from 'superstruct'

const Positive = refine(number(), 'Positive', (value) => value > 0)

const PositiveArray = array(Positive)

Теперь массив допускает только положительные числа:

PositiveArray.assert([1, 2, 3])
PositiveArray.assert([1, -2, 3]) // ошибка

Поведение ошибок в массивах

При валидации массива ошибки привязываются к конкретному индексу элемента. Это позволяет точно локализовать проблему:

  • индекс элемента указывается в пути ошибки
  • проверка прекращается на первом нарушении
  • структура ошибки сохраняет контекст вложенности

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

Работа с неопределёнными значениями внутри массива

Superstruct не допускает undefined как валидное значение, если это явно не предусмотрено структурой:

const MaybeString = union([string(), null])
const List = array(MaybeString)

В этом случае допустимы только строки и null, но не undefined.

Практика построения сложных структур

Комбинирование массивов и объектов позволяет описывать реальные модели данных:

const Comment = object({
  id: number(),
  text: string()
})

const Post = object({
  title: string(),
  comments: array(Comment, { min: 0 })
})

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

Итоговая логика работы массивов

Массив в Superstruct всегда рассматривается как:

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

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