z.input и z.output

Контекст типов в схемах

Библиотека Zod строится вокруг строгого разделения между тем, что попадает на вход схемы, и тем, что схема возвращает после валидации и трансформации.

Каждая схема в Zod имеет два ключевых уровня типов:

  • Input type — тип данных до обработки схемой
  • Output type — тип данных после прохождения валидации, парсинга и трансформаций

Это разделение становится критичным при использовании transform, default, coerce, refine и других операторов, изменяющих форму данных.


z.input

Назначение

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

Разница:

  • Input — строка
  • Output — число

Coerce-типизация

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-типа

Input-тип используется там, где данные ещё не прошли валидацию:

  • HTTP request body
  • form data
  • внешние API
  • JSON из неизвестных источников
function parse(data: z.input<typeof schema>) {
  return schema.parse(data);
}

z.output

Назначение

z.output<T> извлекает тип результата схемы после всех преобразований.

Этот тип соответствует значению, которое возвращает:

  • schema.parse
  • schema.safeParse().data

Базовый пример

const 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;
}

После optional и default

const schema = z.string().optional().default("hello");

Типы:

type Input = z.input<typeof schema>;   // string | undefined
type Output = z.output<typeof schema>; // string

Поведение:

  • input допускает undefined
  • output всегда строка

Сравнение z.input и z.output

Разделение ответственности

Операция 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;
}

Взаимодействие с ZodType

Любая схема в Zod описывается через:

ZodType<TOutput, TDef, TInput>

Где:

  • TOutput — результат
  • TInput — вход
  • TDef — внутренняя конфигурация

z.input и z.output являются безопасным способом извлечения этих типов без доступа к внутренним generic-параметрам.


z.infer и различие с z.output

Часто используется:

z.infer<typeof schema>

Но поведение:

  • z.infer == z.output
  • всегда возвращает выходной тип

Эквивалент:

type A = z.infer<typeof schema>;
type B = z.output<typeof schema>;

Сложные трансформации и цепочки

Многоступенчатые transform

const schema = z
  .string()
  .transform((v) => v.trim())
  .transform((v) => v.length);

Типы:

z.input<typeof schema>  // string
z.output<typeof schema> // number

Effects и refine

const schema = z.string().refine((v) => v.length > 3);

Типы не меняются:

z.input  // string
z.output // string

refine влияет только на валидность, не на структуру.


Использование в API-слоях

Разделение DTO и доменной модели

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>
}

Ошибки при неправильном использовании

Использование output как input

function fn(data: z.output<typeof schema>) {
  schema.parse(data); // ошибка логики типов
}

Проблема:

  • данные уже трансформированы
  • схема ожидает сырой input

Потеря различия при union-схемах

const schema = z.union([
  z.string(),
  z.number().transform(String)
]);

Input и output могут существенно расходиться, и z.input становится единственным точным способом восстановления допустимого входа.


Итоговая модель типов

Концептуально Zod строится на следующем разделении:

  • z.input — форма данных «снаружи системы»
  • parse — граница валидации
  • z.output — форма данных «внутри системы»

Эта модель позволяет отделять:

  • сырые данные
  • валидированные данные
  • преобразованные данные

без потери типовой строгости TypeScript