Работа с динамическими ключами возникает в структурах данных, где заранее неизвестен полный набор свойств объекта. Такие случаи типичны для конфигураций, словарей, маппингов, ответов API и пользовательских настроек. В Superstruct эта задача решается через специализированные структуры, позволяющие описывать «словари» с произвольными ключами и строго типизированными значениями.
В JavaScript объект может выступать как ассоциативный массив, где ключи формируются во время выполнения. Однако при валидации важно не только наличие ключей, но и соответствие их значений заданному типу.
Superstruct предоставляет примитив для таких сценариев —
record, который описывает структуру вида:
Классический пример — словарь числовых значений:
import { record, string, number } from 'superstruct';
const Scores = record(string(), number());
Такая структура означает: ключи — строки, значения — числа.
record является ключевым инструментом для работы с
динамическими ключами. Он принимает два аргумента:
Пример с более строгими ограничениями:
import { record, string, integer } from 'superstruct';
const AgeMap = record(string(), integer());
Здесь формируется отображение имени пользователя на возраст, где:
Любая попытка передать значение другого типа приведёт к ошибке валидации.
Хотя JavaScript допускает практически любые строковые ключи, Superstruct позволяет ограничить допустимые ключи через дополнительные структуры.
Пример ограничения ключей через форматирование строки:
import { record, pattern, number } from 'superstruct';
const HexValues = record(pattern(/[a-f0-9]{6}/i), number());
Здесь ключами могут быть только строки, соответствующие шестнадцатеричному шаблону.
Такой подход часто применяется для:
Когда значения представляют собой сложные объекты, используется вложенная валидация.
import { record, string, object, number } from 'superstruct';
const UserMap = record(
string(),
object({
age: number(),
score: number(),
})
);
Каждое значение теперь обязано быть объектом с определённой формой. Это превращает структуру в динамическую таблицу записей.
Одним из частых применений динамических ключей являются конфигурационные объекты:
import { record, string, boolean } from 'superstruct';
const FeatureFlags = record(string(), boolean());
Такая структура позволяет включать и отключать функциональность по ключу:
{
darkMode: true,
betaUI: false,
analytics: true
}
При этом Superstruct гарантирует, что:
Динамические ключи часто требуют гибких типов значений. В таких
случаях используется union.
import { record, string, union, number, boolean } from 'superstruct';
const FlexibleMap = record(
string(),
union([number(), boolean()])
);
Теперь значения могут быть либо числом, либо булевым типом.
Это полезно в ситуациях, где данные приходят из внешних источников с неоднородной структурой.
В сложных системах значения сами могут содержать динамические ключи:
import { record, string, number } from 'superstruct';
const Matrix = record(
string(),
record(string(), number())
);
Это структура двумерной карты, где:
Такая модель используется для:
В некоторых случаях необходимо сочетать фиксированные поля с
динамическими ключами. Superstruct позволяет комбинировать
object и record.
import { object, string, number, record } from 'superstruct';
const Settings = object({
userId: string(),
preferences: record(string(), number()),
});
Здесь структура содержит:
userIdpreferencesrecord допускает пустые объекты, если не задано
дополнительное ограничение. Это важно учитывать при проектировании
схем:
const EmptyAllowed = record(string(), number());
Допустимыми будут:
{}
Если требуется запрет пустых структур, применяется дополнительная
логика через refine:
import { record, string, number, refine } from 'superstruct';
const NonEmptyRecord = refine(
record(string(), number()),
'NonEmptyRecord',
value => Object.keys(value).length > 0
);
Superstruct позволяет не только проверять, но и преобразовывать данные. При работе с динамическими ключами это полезно для нормализации входных структур.
import { record, string, number, coerce } from 'superstruct';
const CoercedMap = coerce(
record(string(), number()),
record(string(), string()),
value => {
const result = {};
for (const key in value) {
result[key] = Number(value[key]);
}
return result;
}
);
Такой подход применяется при работе с API, где числовые значения приходят в виде строк.
Многие REST и GraphQL API возвращают данные в виде словарей с неизвестными заранее ключами.
const ApiResponse = record(string(), object({
id: string(),
value: number(),
}));
Это позволяет описывать ответы следующего вида:
{
itemA: { id: "itemA", value: 10 },
itemB: { id: "itemB", value: 25 }
}
Несмотря на универсальность, динамические структуры имеют особенности:
Для более сложной логики применяется комбинация refine,
validate и пользовательских функций.
Хотя record уже предполагает динамичность, его можно
комбинировать с частичными структурами для значений:
import { record, string, object, optional, number } from 'superstruct';
const Data = record(
string(),
object({
value: optional(number()),
})
);
Теперь каждое значение может либо содержать value, либо
не содержать его вовсе.
Наиболее типичный сценарий — хранение коллекций сущностей:
const Entities = record(
string(),
object({
name: string(),
rating: number(),
active: boolean(),
})
);
Такая структура представляет собой индексированную коллекцию, где ключ — идентификатор, а значение — полная модель объекта.
Динамические ключи в Superstruct строятся вокруг идеи обобщённого словаря, в котором:
Это делает record центральным инструментом для
моделирования гибких данных без потери контроля над типами и
валидацией.