Утилиты для работы со схемами

Одной из ключевых особенностей Zod является возможность строить сложные схемы из более простых за счёт композиции. Схемы в Zod не являются статичными структурами — они поддерживают цепочки методов, возвращающих новые экземпляры схем без изменения исходных.

import { z } from "zod";

const baseUser = z.object({
  id: z.string(),
  email: z.string().email(),
});

На основе базовой схемы можно создавать расширения без дублирования описаний.

const adminUser = baseUser.extend({
  role: z.literal("admin"),
});

Такой подход формирует устойчивую архитектуру валидации, где базовые контракты данных переиспользуются на всех уровнях приложения.


Модификация схем объектов

extend

Метод extend используется для добавления новых полей к объектной схеме:

const user = z.object({
  id: z.string(),
});

const userWithName = user.extend({
  name: z.string(),
});

Исходная схема не изменяется, создаётся новая.


pick и omit

pick извлекает часть полей из схемы:

const userPublic = userWithName.pick({
  id: true,
  name: true,
});

omit исключает поля:

const userPrivate = userWithName.omit({
  id: true,
});

Эти утилиты часто применяются для разделения публичных и внутренних DTO.


partial и required

partial делает все поля необязательными:

const partialUser = userWithName.partial();

required восстанавливает обязательность:

const requiredUser = partialUser.required();

Также возможно частичное применение:

const onlyNameOptio nal = userWithName.partial({
  name: true,
});

strict, passthrough, strip

Контроль поведения лишних полей:

const strictSchema = z.object({
  id: z.string(),
}).strict();
  • strict — запрещает лишние ключи
  • passthrough — сохраняет лишние ключи
  • strip — удаляет лишние ключи (поведение по умолчанию)
const passthroughSchema = z.object({
  id: z.string(),
}).passthrough();

Преобразование данных

transform

Метод transform позволяет менять форму данных после валидации:

const stringToNumber = z.string().transform((val) => Number(val));

Схема сначала валидирует строку, затем преобразует её в число.

const userId = z.string().uuid().transform((id) => ({
  id,
  createdAt: Date.now(),
}));

preprocess

preprocess применяется до валидации:

const numberSchema = z.preprocess((val) => {
  if (typeof val === "string") return Number(val);
  return val;
}, z.number());

Это полезно для нормализации входных данных.


pipe

pipe соединяет две схемы в цепочку преобразований:

const schema = z.string().pipe(z.number()).pipe(z.boolean());

Каждый этап получает результат предыдущего.


Расширенная валидация

refine

refine добавляет пользовательскую проверку:

const password = z.string().refine((val) => val.length >= 8, {
  message: "Слишком короткий пароль",
});

Используется для простых бизнес-правил.


superRefine

superRefine предоставляет доступ к контексту ошибок:

const schema = z.object({
  password: z.string(),
  confirm: z.string(),
}).superRefine((data, ctx) => {
  if (data.password !== data.confirm) {
    ctx.addIssue({
      path: ["confirm"],
      message: "Пароли не совпадают",
      code: z.ZodIssueCode.custom,
    });
  }
});

Позволяет добавлять множественные ошибки и указывать путь.


Значения по умолчанию и обработка ошибок

default

default задаёт значение при отсутствии поля:

const schema = z.object({
  role: z.string().default("user"),
});

Если role не передан, он будет установлен автоматически.


catch

catch используется для fallback-значений при ошибке парсинга:

const schema = z.number().catch(0);

Если значение невалидно, возвращается 0.

Также поддерживается функция:

z.number().catch(() => -1);

Опциональность и nullable

optional

const schema = z.string().optional();

Поле может отсутствовать.


nullable

const schema = z.string().nullable();

Поле может быть null, но не undefined.


сочетания

const schema = z.string().optional().nullable();

Допускает string | undefined | null.


Ленивые схемы

lazy

Используется для рекурсивных структур:

const Category = z.lazy(() =>
  z.object({
    name: z.string(),
    children: z.array(Category).optional(),
  })
);

Без lazy невозможно описать самоссылочные структуры.


Брендинг и иммутабельность

brand

Позволяет создавать номинальные типы:

const UserId = z.string().brand<"UserId">();

Хотя на уровне runtime это строка, TypeScript различает тип.


readonly

const schema = z.array(z.string()).readonly();

Используется для запрета мутаций на уровне типов.


Описания схем

describe

Добавляет мета-информацию:

const schema = z.string().describe("Имя пользователя");

Описание может использоваться для генерации документации или форм.


Объединение схем

merge

Объединяет объектные схемы:

const a = z.object({ id: z.string() });
const b = z.object({ name: z.string() });

const merged = a.merge(b);

intersection

Создаёт пересечение типов:

const schema = z.intersection(a, b);

Результат должен соответствовать обеим схемам одновременно.


union

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

Поддерживает одно из нескольких значений.


discriminatedUnion

Оптимизированный вариант union с дискриминатором:

const schema = z.discriminatedUnion("type", [
  z.object({ type: z.literal("a"), value: z.string() }),
  z.object({ type: z.literal("b"), value: z.number() }),
]);

Позволяет эффективно различать ветви.


Работа с кастомными схемами

custom

const schema = z.custom((val) => typeof val === "bigint");

Используется для произвольной логики проверки.


Практика композиции и построения контрактов

Комбинирование утилит формирует выразительный слой описания данных:

const base = z.object({
  id: z.string(),
  createdAt: z.string(),
});

const updateSchema = base
  .partial()
  .extend({
    updatedAt: z.string().optional(),
  })
  .refine((data) => Object.keys(data).length > 0);

Такие конструкции позволяют описывать изменения состояния данных без дублирования структур.


Ленивое преобразование и цепочки

Схемы могут быть связаны в цепочки трансформаций и проверок:

const schema = z.string()
  .transform((val) => val.trim())
  .refine((val) => val.length > 0)
  .pipe(z.string().min(3));

Каждый этап отвечает за отдельный аспект обработки данных: нормализацию, валидацию и финальную форму.