Библиотека Zod строится вокруг строгого разделения между тем, что попадает на вход схемы, и тем, что схема возвращает после валидации и трансформации.
Каждая схема в Zod имеет два ключевых уровня типов:
Это разделение становится критичным при использовании
transform, default, coerce,
refine и других операторов, изменяющих форму данных.
z.input<T> извлекает тип входных данных
схемы, то есть тот тип, который допустим до выполнения всех
преобразований.
Это особенно важно, когда схема меняет структуру данных.
import { z } from "zod";
const schema = z.string();
type Input = z.input<typeof schema>; // string
В простом случае входной и выходной тип совпадают.
Ключевая особенность проявляется при использовании
transform.
const schema = z.string().transform((val) => val.length);
Типы:
type Input = z.input<typeof schema>; // string
type Output = z.output<typeof schema>; // number
Разница:
const schema = z.coerce.number();
Типы:
type Input = z.input<typeof schema>; // string | number | boolean
type Output = z.output<typeof schema>; // number
z.coerce.number() принимает широкий набор входных
значений, но нормализует их до числа.
Input-тип используется там, где данные ещё не прошли валидацию:
function parse(data: z.input<typeof schema>) {
return schema.parse(data);
}
z.output<T> извлекает тип результата схемы
после всех преобразований.
Этот тип соответствует значению, которое возвращает:
schema.parseschema.safeParse().dataconst schema = z.number();
type Output = z.output<typeof schema>; // number
const schema = z.string().transform((val) => ({
length: val.length,
value: val
}));
Тип:
type Output = z.output<typeof schema>;
Результат:
{
length: number;
value: string;
}
const schema = z.string().optional().default("hello");
Типы:
type Input = z.input<typeof schema>; // string | undefined
type Output = z.output<typeof schema>; // string
Поведение:
undefined| Операция | z.input | z.output |
|---|---|---|
| До transform | ✔ | ✖ |
| После transform | ✖ | ✔ |
| До default | ✔ | ✖ |
| После default | ✖ | ✔ |
| Coerce вход | широкий | нормализованный |
const schema = z.object({
id: z.string().transform(Number),
age: z.coerce.number().optional().default(18)
});
Типы:
type Input = z.input<typeof schema>;
{
id: string;
age?: string | number;
}
type Output = z.output<typeof schema>;
{
id: number;
age: number;
}
Любая схема в Zod описывается через:
ZodType<TOutput, TDef, TInput>
Где:
TOutput — результатTInput — входTDef — внутренняя конфигурацияz.input и z.output являются безопасным
способом извлечения этих типов без доступа к внутренним
generic-параметрам.
Часто используется:
z.infer<typeof schema>
Но поведение:
z.infer == z.outputЭквивалент:
type A = z.infer<typeof schema>;
type B = z.output<typeof schema>;
const schema = z
.string()
.transform((v) => v.trim())
.transform((v) => v.length);
Типы:
z.input<typeof schema> // string
z.output<typeof schema> // number
const schema = z.string().refine((v) => v.length > 3);
Типы не меняются:
z.input // string
z.output // string
refine влияет только на валидность, не на структуру.
const userSchema = z.object({
id: z.string().transform(Number),
email: z.string().email()
});
type RequestDTO = z.input<typeof userSchema>;
type User = z.output<typeof userSchema>;
function createUser(data: z.input<typeof userSchema>) {
const user = userSchema.parse(data);
return user; // z.output<typeof userSchema>
}
function fn(data: z.output<typeof schema>) {
schema.parse(data); // ошибка логики типов
}
Проблема:
const schema = z.union([
z.string(),
z.number().transform(String)
]);
Input и output могут существенно расходиться, и z.input
становится единственным точным способом восстановления допустимого
входа.
Концептуально Zod строится на следующем разделении:
Эта модель позволяет отделять:
без потери типовой строгости TypeScript