В JavaScript структуры Map и Set
представляют собой специализированные коллекции, отличающиеся от обычных
объектов и массивов строгими гарантиями уникальности ключей и значений.
В контексте типизации и валидации данных библиотека Superstruct
предоставляет встроенные механизмы для работы с этими структурами через
map() и set().
Подход Superstruct заключается в том, чтобы не только проверять форму данных, но и рекурсивно валидировать содержимое коллекций, включая вложенные структуры.
Map в Superstruct описывается как структура, где
отдельно задаются правила для:
Сигнатура:
map(valueStruct, keyStruct)
Если keyStruct не указан, по умолчанию предполагается
any.
import { map, string, number, validate } from "superstruct";
const UserScores = map(number(), string());
const data = new Map([
["Alice", 10],
["Bob", 20]
]);
const [error, result] = validate(data, UserScores);
В данном примере:
Любое отклонение от схемы приводит к ошибке валидации.
Часто требуется Map, где ключи — идентификаторы, а значения — сложные объекты:
import { map, object, string, number } from "superstruct";
const UserStruct = object({
name: string(),
age: number()
});
const Users = map(UserStruct, string());
Здесь:
Map может содержать другие Map, создавая многослойные структуры:
const Matrix = map(
map(number()),
string()
);
Такая конструкция описывает структуру вида:
При валидации учитываются:
Map, а не объект с похожей
структуройSet представляет собой коллекцию уникальных значений. В
Superstruct используется функция:
set(valueStruct)
import { set, number, validate } from "superstruct";
const UniqueNumbers = set(number());
const data = new Set([1, 2, 3, 3]);
const [error, result] = validate(data, UniqueNumbers);
Хотя Set в JavaScript автоматически удаляет дубликаты,
валидация в Superstruct фокусируется на типе каждого элемента, а не на
уникальности (уникальность гарантируется самой структурой Set).
import { set, object, string } from "superstruct";
const Tag = object({
name: string()
});
const Tags = set(Tag);
Каждый элемент множества должен соответствовать структуре
Tag.
При работе с Set важно учитывать:
import { map, set, string, number } from "superstruct";
const UserPermissions = map(
set(string()),
string()
);
Структура описывает:
const ConfigSet = set(
map(number(), string())
);
Такая конструкция описывает набор конфигурационных мап.
Superstruct поддерживает произвольную глубину вложенности структур. При этом каждое значение проходит независимую проверку согласно своей части схемы.
Пример глубокой вложенности:
const DeepStruct = map(
set(
map(
number(),
string()
)
),
string()
);
Такие структуры часто используются при моделировании:
Ошибки валидации формируются на уровне конкретного элемента:
Это позволяет точно локализовать проблему в сложных структурах.
В некоторых случаях входные данные могут быть приведены к Map или Set перед валидацией:
const raw = {
a: 1,
b: 2
};
const data = new Map(Object.entries(raw));
Далее структура проверяется через Superstruct без изменений.
Если вместо Map или Set передаётся другой тип (например, Object или Array), валидация завершается ошибкой на уровне структуры контейнера, до проверки содержимого.
Пример:
validate({ a: 1 }, map(number(), string()));
Результат — ошибка типа контейнера, так как ожидается
Map.
Map и Set могут комбинироваться с refine, позволяя
добавлять дополнительные ограничения:
import { set, number, refine } from "superstruct";
const EvenNumbers = refine(set(number()), "EvenNumbers", (value) => {
for (const v of value) {
if (v % 2 !== 0) return false;
}
return true;
});
Такая конструкция добавляет бизнес-логику поверх базовой типизации.
Map и Set не сериализуются напрямую в JSON:
Object.fromEntriesЭто важно при работе с API, где данные проходят через Superstruct до отправки или после получения.
const json = JSON.stringify([...mySet]);
или
const json = JSON.stringify(Object.fromEntries(myMap));
Каждая из этих ситуаций приводит к ошибкам на этапе валидации структуры, а не выполнения бизнес-логики.