Символ (Symbol) в JavaScript представляет собой
примитивный тип данных, создающий гарантированно уникальные значения.
Даже при одинаковом описании каждый вызов Symbol()
возвращает новое значение, не равное никакому другому символу.
const a = Symbol("id");
const b = Symbol("id");
a === b; // false
Ключевая особенность символов заключается в их идентичности на уровне ссылки, а не содержимого. Это делает их удобным инструментом для создания скрытых ключей объектов, метаданных и уникальных маркеров типов.
Существует также глобальный реестр символов через
Symbol.for, где значения повторно используются:
const a = Symbol.for("shared");
const b = Symbol.for("shared");
a === b; // true
Эта двойственность (локальные и глобальные символы) напрямую влияет на моделирование данных в схемах валидации.
Библиотека Zod предоставляет примитив z.symbol() для
описания значений типа Symbol.
import { z } from "zod";
const SymbolSchema = z.symbol();
Данный тип проверяет, что значение действительно является JavaScript Symbol:
SymbolSchema.parse(Symbol("ok")); // успешно
SymbolSchema.parse("not symbol"); // ошибка
При этом Zod не ограничивает символ конкретным значением — проверяется только тип.
В реальных схемах часто требуется не просто «любой символ», а строго определённый уникальный маркер. Это важно при моделировании:
Для этого используется комбинация Symbol() и
z.literal().
Zod позволяет использовать z.literal для строгого
сравнения значений, включая символы:
const UNIQUE = Symbol("unique");
const Schema = z.literal(UNIQUE);
Schema.parse(UNIQUE); // корректно
Schema.parse(Symbol("unique")); // ошибка
Ключевой момент: сравнение происходит по ссылке, поэтому только конкретный экземпляр символа считается допустимым значением.
При использовании Symbol.for появляется возможность
создавать стабильные значения между модулями или даже разными частями
приложения.
const TOKEN = Symbol.for("auth.token");
const Schema = z.literal(TOKEN);
Такой подход применяется в системах, где требуется гарантированное совпадение идентификатора без экспорта констант.
Однако следует учитывать: глобальный реестр делает символы разделяемыми, что снижает степень их «уникальности» как маркеров.
Символы могут использоваться как дискриминаторы, хотя чаще
применяются строки. В Zod это реализуется через
z.discriminatedUnion, но ключ дискриминации должен быть
сравнимым значением.
Пример с символами:
const A = Symbol("A");
const B = Symbol("B");
const SchemaA = z.object({
kind: z.literal(A),
value: z.number()
});
const SchemaB = z.object({
kind: z.literal(B),
value: z.string()
});
const Union = z.union([SchemaA, SchemaB]);
Такой подход создаёт строгую типизацию состояний, где невозможно случайное совпадение строковых идентификаторов.
Несмотря на выразительность, символы имеют ряд ограничений в контексте сериализации и передачи данных:
JSON.stringify({ key: Symbol("x") }); // {}
Это делает их пригодными только для внутренних структур и runtime-логики.
Помимо символов, Zod предоставляет более универсальный механизм —
z.literal, который поддерживает:
null и undefinedconst A = z.literal("A");
const B = z.literal(1);
const C = z.literal(Symbol.for("C"));
Литералы часто применяются как более сериализуемая альтернатива символам, особенно в API-схемах.
В Zod существует механизм брендинга, позволяющий создавать логически уникальные типы поверх примитивов. Это особенно полезно там, где символы не подходят из-за проблем сериализации.
const UserId = z.string().brand<"UserId">();
const id = UserId.parse("123");
Бренд не влияет на runtime-значение, но добавляет строгую типизацию на уровне TypeScript.
Символы в этом контексте выступают как runtime-аналог брендинга,
тогда как brand() — как compile-time механизм.
В некоторых архитектурах символы применяются для создания невидимых свойств объектов, которые не участвуют в стандартных операциях.
const INTERNAL = Symbol("internal");
const schema = z.object({
name: z.string()
}).transform((data) => {
data[INTERNAL] = true;
return data;
});
Zod при этом не различает «видимые» и «скрытые» ключи — символы просто проходят как обычные значения, если они описаны схемой.
При проектировании систем часто комбинируются разные подходы:
const STATE_IDLE = Symbol.for("state.idle");
const STATE_LOADING = Symbol.for("state.loading");
const StateSchema = z.union([
z.literal(STATE_IDLE),
z.literal(STATE_LOADING)
]);
Такой подход создаёт строгую модель состояния, устойчивую к случайным совпадениям строк.
Zod выполняет проверку символов через строгую идентичность:
z.symbol() — проверка
typeof value === "symbol"z.literal(symbol) — проверка по ссылкеЭто делает символы максимально предсказуемыми в рамках runtime-валидации, но требует аккуратного управления их созданием и экспортом между модулями.