Map и Set

Валидация коллекций Map и Set

В JavaScript структуры Map и Set представляют собой специализированные коллекции, отличающиеся от обычных объектов и массивов строгими гарантиями уникальности ключей и значений. В контексте типизации и валидации данных библиотека Superstruct предоставляет встроенные механизмы для работы с этими структурами через map() и set().

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


Структура Map в Superstruct

Базовая концепция

Map в Superstruct описывается как структура, где отдельно задаются правила для:

  • ключей (key struct)
  • значений (value struct)

Сигнатура:

map(valueStruct, keyStruct)

Если keyStruct не указан, по умолчанию предполагается any.


Простейшая валидация Map

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());

Здесь:

  • ключи — строки (например, ID пользователя)
  • значения — объекты пользователя

Вложенные структуры Map

Map может содержать другие Map, создавая многослойные структуры:

const Matrix = map(
  map(number()),
  string()
);

Такая конструкция описывает структуру вида:

  • внешний ключ — строка
  • значение — Map
  • внутренний Map содержит числовые значения

Особенности проверки Map

При валидации учитываются:

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

Структура Set в Superstruct

Базовая концепция

Set представляет собой коллекцию уникальных значений. В Superstruct используется функция:

set(valueStruct)

Простая валидация Set

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).


Set со сложными типами

import { set, object, string } from "superstruct";

const Tag = object({
  name: string()
});

const Tags = set(Tag);

Каждый элемент множества должен соответствовать структуре Tag.


Ограничения и поведение Set

При работе с Set важно учитывать:

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

Сочетание Map и Set в сложных структурах

Map, содержащий Set

import { map, set, string, number } from "superstruct";

const UserPermissions = map(
  set(string()),
  string()
);

Структура описывает:

  • ключ: строка (например, роль)
  • значение: множество разрешений

Set, содержащий Map

const ConfigSet = set(
  map(number(), string())
);

Такая конструкция описывает набор конфигурационных мап.


Рекурсивная валидация и вложенность

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

Пример глубокой вложенности:

const DeepStruct = map(
  set(
    map(
      number(),
      string()
    )
  ),
  string()
);

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

  • графов данных
  • кешей
  • конфигурационных деревьев
  • индексированных коллекций

Обработка ошибок в Map и Set

Ошибки валидации формируются на уровне конкретного элемента:

  • для Map указывается путь: ключ → значение
  • для Set указывается индекс элемента внутри множества

Это позволяет точно локализовать проблему в сложных структурах.


Приведение типов и преобразования

В некоторых случаях входные данные могут быть приведены к 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:

  • Map требует преобразования через Object.fromEntries
  • Set требует преобразования в массив

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

const json = JSON.stringify([...mySet]);

или

const json = JSON.stringify(Object.fromEntries(myMap));

Типичные ошибки при использовании Map и Set

  • использование обычного объекта вместо Map
  • передача массива вместо Set
  • несоответствие типов ключей или значений
  • попытка сериализации без преобразования

Каждая из этих ситуаций приводит к ошибкам на этапе валидации структуры, а не выполнения бизнес-логики.