Литеральные типы в Zod представляют собой способ описания строго
фиксированных значений, которые допустимы в данных. В отличие от
примитивных схем вроде z.string() или
z.number(), литералы задают конкретное значение, которое
должно совпасть полностью.
Основной примитив для работы с такими значениями —
z.literal(). Он позволяет ограничить
допустимое значение одним конкретным вариантом.
import { z } from "zod";
const schema = z.literal("success");
schema.parse("success"); // OK
schema.parse("error"); // ошибка
Такой подход полезен там, где значение не является произвольным, а строго определено контрактом системы.
Литеральная схема выполняет строгое сравнение значения. Используется
эквивалентность ===, поэтому тип и значение должны
совпадать полностью.
z.literal(42).parse(42); // OK
z.literal(42).parse("42"); // ошибка
Для объектов и массивов литералы применяются редко, поскольку сравнение происходит по ссылке, а не по структуре:
const obj = { a: 1 };
z.literal(obj).parse(obj); // OK
z.literal({ a: 1 }).parse({ a: 1 }); // ошибка
Такое поведение делает литералы наиболее полезными для примитивов: строк, чисел, булевых значений.
Литералы часто используются для фиксированных флагов или ограниченных числовых значений.
const activeSchema = z.literal(true);
activeSchema.parse(true); // OK
activeSchema.parse(false); // ошибка
Числовые литералы применяются в ситуациях, где значение играет роль кода состояния:
const statusCode = z.literal(200);
statusCode.parse(200); // OK
statusCode.parse(404); // ошибка
Наиболее распространённый сценарий — объединение нескольких
литеральных значений. Для этого используется z.union().
const roleSchema = z.union([
z.literal("admin"),
z.literal("user"),
z.literal("guest")
]);
roleSchema.parse("admin"); // OK
roleSchema.parse("root"); // ошибка
Такой подход формирует ограниченный набор допустимых значений без использования enum.
Zod предоставляет отдельную абстракцию — z.enum(),
которая по сути является синтаксическим упрощением для набора строковых
литералов.
const roleSchema = z.enum(["admin", "user", "guest"]);
roleSchema.parse("admin"); // OK
roleSchema.parse("root"); // ошибка
Ключевые отличия между z.enum() и
z.union(z.literal()):
z.enum() работает только со строкамиz.union(z.literal()) поддерживает любые типы (числа,
boolean, mixed)z.enum() генерирует более удобные типы TypeScriptunion обеспечивает большую гибкостьЛитеральные значения часто применяются в дискриминированных объединениях. Это один из ключевых паттернов для типизации структурированных данных.
const eventSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("click"),
x: z.number(),
y: z.number()
}),
z.object({
type: z.literal("scroll"),
scrollTop: z.number()
})
]);
Поле type выступает дискриминатором, позволяющим
автоматически сужать тип.
Zod тесно интегрируется с TypeScript, и литеральные типы играют важную роль в выводе точных типов.
const schema = z.literal("active");
type Status = z.infer<typeof schema>;
// Status = "active"
При объединении литералов формируется union-тип:
const schema = z.union([
z.literal("a"),
z.literal("b"),
z.literal("c")
]);
type Letters = z.infer<typeof schema>;
// "a" | "b" | "c"
Это позволяет строить строгие контрактные типы без ручного описания TypeScript-интерфейсов.
Одно из практических применений — описание конфигураций, где допустимые значения ограничены фиксированными вариантами.
const configSchema = z.object({
environment: z.union([
z.literal("development"),
z.literal("staging"),
z.literal("production")
])
});
Такой подход исключает возможность передачи произвольных строк и делает контракт явным.
Литеральные типы часто используются для описания протоколов взаимодействия между клиентом и сервером.
const requestSchema = z.object({
method: z.union([
z.literal("GET"),
z.literal("POST"),
z.literal("PUT"),
z.literal("DELETE")
]),
path: z.string()
});
Это гарантирует, что метод запроса всегда соответствует допустимому HTTP-набору.
Литеральные значения часто выступают частью более сложных структур.
const responseSchema = z.object({
status: z.literal("ok"),
data: z.object({
id: z.number(),
name: z.string()
})
});
В этом случае литерал фиксирует состояние ответа, а вложенная структура описывает полезную нагрузку.
Хотя литералы сами по себе не трансформируют данные, они часто
участвуют в цепочках с transform, где значение используется
как триггер логики.
const schema = z.literal("double").transform(() => 2);
В этом случае входное значение служит маркером для преобразования.
Литералы могут комбинироваться с другими схемами через
z.intersection() или вложенные объекты, создавая более
сложные контракты.
const base = z.object({
type: z.literal("event")
});
const clickEvent = z.intersection(
base,
z.object({
x: z.number(),
y: z.number()
})
);
Такие конструкции часто используются для построения строгих и расширяемых API-моделей.
Одной из распространённых проблем является попытка использовать литералы для структурных объектов:
z.literal({ a: 1 }); // почти всегда не работает как ожидается
Также ошибка возникает при ожидании нестрогого сравнения:
z.literal(1).parse("1"); // ошибка, несмотря на "похожесть"
Литеральные типы не предназначены для приведения типов, только для строгой проверки совпадения.
Литеральные схемы не выполняют нормализацию или приведение. Любое отклонение от ожидаемого значения приводит к ошибке валидации.
z.literal(true).safeParse(1);
// success: false
Это делает их предсказуемыми и удобными для контрактной валидации.
Литеральные типы выполняют функцию фиксированных маркеров внутри схем данных. Они часто используются как:
Их сила заключается не в гибкости, а в жёсткой фиксации допустимых значений, что позволяет строить строгие и самодокументируемые схемы данных.