Разнородный массив в контексте валидации данных — это структура, элементы которой не ограничены одним типом. В JavaScript такие массивы встречаются повсеместно: от конфигурационных списков до данных, полученных из внешних источников, где элементы могут представлять собой строки, числа, объекты или даже вложенные структуры.
При работе с библиотекой Superstruct подобные случаи требуют явного описания допустимых вариантов значений, поскольку базовая валидация массивов предполагает однородность элементов. Основная задача — определить правила, по которым каждый элемент массива может принадлежать одному из нескольких структурных типов.
В Superstruct массив описывается через array(structure),
где structure задаёт тип каждого элемента:
import { array, number } from 'superstruct';
const Numbers = array(number());
Такое определение допускает только массив чисел. Любое отклонение приведёт к ошибке валидации.
Однако при переходе к разнородным массивам такая модель становится недостаточной, поскольку требуется описать несколько возможных структур одновременно.
Основным инструментом работы с разнородными массивами является
union. Он позволяет объединять несколько структурных типов
в один допустимый набор.
import { array, union, string, number } from 'superstruct';
const MixedArray = array(
union([string(), number()])
);
В этом случае каждый элемент массива может быть либо строкой, либо числом. Валидация проходит по принципу проверки каждого элемента против всех допустимых структур.
При использовании union внутри array
происходит поэлементная проверка:
union.union последовательно проверяет соответствие всем
указанным структурам.Такой подход обеспечивает гибкость, но увеличивает вычислительную стоимость валидации при больших массивах или сложных структурах.
На практике наиболее распространённый случай — массив объектов разной структуры. Например, список событий, где каждый элемент описывает свой тип действия.
import { array, union, object, string, number } from 'superstruct';
const ClickEvent = object({
type: string(),
x: number(),
y: number(),
});
const ScrollEvent = object({
type: string(),
delta: number(),
});
const Events = array(
union([ClickEvent, ScrollEvent])
);
Здесь каждый элемент массива должен соответствовать одной из структур событий. Такой подход позволяет строго контролировать форму данных при сохранении гибкости.
При работе с разнородными массивами часто используется дискриминатор
— поле, определяющее тип объекта. Обычно это type или
аналогичный ключ.
const ClickEvent = object({
type: string(),
x: number(),
y: number(),
});
const ScrollEvent = object({
type: string(),
delta: number(),
});
Однако без дополнительной проверки type остаётся обычной
строкой. Для более строгой схемы применяется уточнение через
literal.
import { literal } from 'superstruct';
const ClickEvent = object({
type: literal('click'),
x: number(),
y: number(),
});
const ScrollEvent = object({
type: literal('scroll'),
delta: number(),
});
Это позволяет не только различать структуры, но и снижает вероятность ошибок при обработке данных.
Superstruct поддерживает композицию, что позволяет строить сложные структуры с вложенными массивами:
const Data = array(
union([
array(number()),
object({
label: string(),
values: array(number()),
}),
])
);
Здесь элементы массива могут быть либо массивом чисел, либо объектом с вложенным массивом чисел. Подобные конструкции часто используются при обработке гибридных API-ответов.
Использование union в массивах влияет на
производительность в зависимости от количества альтернативных
структур:
Для оптимизации часто используется:
В ряде случаев более эффективным решением является отказ от разнородного массива в пользу нескольких однородных:
const Data = object({
numbers: array(number()),
strings: array(string()),
});
Такой подход снижает сложность валидации и повышает предсказуемость структуры данных, особенно при больших объёмах информации.
Superstruct позволяет добавлять пользовательскую логику через
refine, что особенно полезно при сложных условиях
принадлежности элемента к одному из типов:
import { refine } from 'superstruct';
const PositiveNumber = refine(number(), 'PositiveNumber', (value) => {
return value > 0;
});
В сочетании с union это позволяет создавать более точные
ограничения:
const Mixed = array(
union([string(), PositiveNumber])
);
При обработке внешних данных часто встречаются случаи, когда
структура массива заранее неизвестна. В таких ситуациях используется
комбинация any и последующей валидации:
import { any } from 'superstruct';
const LooseArray = array(any());
Далее элементы могут быть преобразованы и проверены отдельно. Такой подход применяется как промежуточный слой при интеграции с внешними API.
Наиболее устойчивые схемы для разнородных массивов строятся по нескольким принципам:
union;union внутри
union;Эти принципы позволяют сохранять баланс между гибкостью и строгостью проверки данных.
Разнородные массивы часто используются вместе с дополнительными ограничениями на сам массив:
import { size } from 'superstruct';
const LimitedMixedArray = size(
array(union([string(), number()])),
1,
10
);
Здесь задаётся ограничение на количество элементов, что дополнительно снижает риск неконтролируемого роста данных.
При нарушении структуры разнородного массива Superstruct возвращает детализированное описание ошибки, включая:
Это позволяет точно локализовать проблему даже в сложных вложенных структурах, где один массив содержит элементы разных типов и уровней вложенности.