Generic-типы в контексте Zod позволяют создавать переиспользуемые, параметризованные структуры валидации, которые адаптируются под различные входные и выходные типы данных. Основная идея заключается в том, чтобы абстрагировать схему от конкретного типа и сделать её универсальной без потери строгости TypeScript-типизации.
Базовый класс для всех схем в Zod представлен как:
ZodType<Output, Def, Input>
где:
Эта тройная параметризация является фундаментом работы generics в Zod и позволяет точно описывать поведение схемы на уровне типов.
Одним из основных применений generic-подхода является создание фабрик схем.
import { z } from "zod";
function createNullableSchema<T extends z.ZodTypeAny>(schema: T) {
return schema.nullable();
}
Здесь T extends z.ZodTypeAny обеспечивает, что в функцию
может быть передана любая Zod-схема, а возвращаемое значение сохраняет
корректную типизацию.
Generic-подход часто применяется при создании объектов со строго типизированной структурой:
function createEntitySchema<T extends z.ZodRawShape>(shape: T) {
return z.object(shape);
}
Использование:
const userSchema = createEntitySchema({
id: z.string(),
age: z.number(),
});
Тип T фиксирует структуру объекта, а Zod автоматически
выводит результирующий тип.
Generic-типы особенно важны при комбинировании transform
и refine.
function withParsedNumber<T extends z.ZodTypeAny>(schema: T) {
return schema.transform((val) => Number(val));
}
Типизация при этом сохраняет связь между входным и выходным типом, если трансформация явно описана.
z.ZodTypeAny используется как верхнеуровневое
ограничение для любых схем:
function wrapSchema<T extends z.ZodTypeAny>(schema: T) {
return schema.optional();
}
Этот тип играет ключевую роль в построении универсальных утилит, поскольку охватывает:
ZodStringZodNumberZodObjectZodArrayПри построении вложенных структур generics позволяют сохранять типовую целостность:
function createResponseSchema<T extends z.ZodTypeAny>(dataSchema: T) {
return z.object({
success: z.boolean(),
data: dataSchema,
});
}
Результирующий тип автоматически зависит от переданного
dataSchema, что позволяет создавать типобезопасные
API-ответы.
Zod активно использует TypeScript inference, который усиливается generic-параметрами:
type Infer<T extends z.ZodTypeAny> = z.infer<T>;
Это позволяет извлекать типы из схем без ручного описания структур.
При работе с объединениями типов generics позволяют параметризовать каждую ветку:
function createEventSchema<T extends string>(type: T) {
return z.object({
type: z.literal(type),
payload: z.any(),
});
}
Комбинация таких схем формирует строгие discriminated unions.
Типичная архитектура валидации строится через цепочку generic-функций:
function withMeta<T extends z.ZodTypeAny>(schema: T) {
return z.object({
meta: z.object({
timestamp: z.number(),
}),
data: schema,
});
}
Такие конструкции позволяют формировать единый стандарт структуры данных в приложении.
При усложнении generic-цепочек TypeScript может терять точность вывода. Это особенно заметно при:
transformrefinemerge и intersectionДля стабилизации типов применяется явное указание
ZodType<Output, Def, Input>.
В случаях, когда inference недостаточен, схема описывается полностью:
const schema: z.ZodType<{ id: string }, any, unknown> = z.object({
id: z.string(),
});
Такой подход фиксирует контракт и исключает неоднозначность в типизации.
Создание расширяемых схем базируется на комбинировании generics и
extend:
function extendSchema<T extends z.ZodRawShape>(base: T) {
return z.object(base).extend({
createdAt: z.date(),
});
}
Это позволяет строить иерархические модели данных без потери типовой строгости.
Сложные схемы часто формируются через композицию функций:
function pipeSchema<T extends z.ZodTypeAny>(schema: T) {
return withMeta(schema).optional();
}
Каждый уровень композиции сохраняет связь типов, если generics корректно протянуты через цепочку.
Generic-типы позволяют моделировать динамическое поведение схем:
function conditionalSchema<T extends z.ZodTypeAny>(
condition: boolean,
schema: T
) {
return condition ? schema : z.any();
}
Хотя структура становится динамической, типовая часть сохраняется
через T.
Чрезмерное использование generics приводит к:
Баланс достигается через ограничение универсальности и явное указание типов в критических местах.
Основная ценность generics в Zod заключается в строгом связывании:
Это формирует единый контракт, который исключает рассинхронизацию между runtime-валидацией и compile-time типами.