Частичная валидация

Частичная валидация применяется в ситуациях, когда входные данные содержат не полный набор полей объекта, а только их подмножество. Это характерно для PATCH-запросов, форм редактирования, обновлений сущностей и любых сценариев, где требуется изменить лишь часть структуры, не затрагивая остальные поля.

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


Базовая концепция partial-схем

Обычная схема объекта в Zod описывает строгую структуру, где каждое поле является обязательным:

import { z } from "zod";

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string(),
});

Такая схема требует наличия всех трёх полей при валидации.

Метод .partial() изменяет поведение схемы:

const PartialUserSchema = UserSchema.partial();

После применения .partial() каждое поле становится необязательным, сохраняя при этом типизацию и ограничения значений.


Поведение .partial() на уровне типов

.partial() преобразует:

{
  id: number;
  name: string;
  email: string;
}

в:

{
  id?: number;
  name?: string;
  email?: string;
}

Ключевой момент заключается в том, что поля не удаляются из схемы, а получают статус optional. Это означает, что при наличии значения оно всё равно проходит валидацию.


Частичная валидация и safeParse

При использовании частичной схемы метод safeParse становится основным инструментом обработки входных данных:

const result = PartialUserSchema.safeParse({
  email: "test@mail.com",
});

Результат будет успешным, даже если отсутствуют id и name, поскольку они не обязательны.

При этом, если поле присутствует, но не соответствует типу, валидация всё равно проваливается:

PartialUserSchema.safeParse({
  name: 123,
});

Несмотря на частичность, типовая проверка остаётся строгой.


Ограничения partial-объектов

.partial() не изменяет следующие аспекты:

  • типы значений полей
  • вложенные структуры (по умолчанию)
  • валидацию union-типа внутри полей
  • кастомные refinements

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


Глубокая частичная валидация

Для вложенных объектов используется deepPartial:

const Schema = z.object({
  user: z.object({
    profile: z.object({
      age: z.number(),
      city: z.string(),
    }),
  }),
});

const DeepPartialSchema = Schema.deepPartial();

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


Разница между partial и optional

Важно различать два уровня:

  • .partial() — трансформирует объект, делая все его поля необязательными
  • z.optional() — применяется к конкретному полю

Пример:

const Schema = z.object({
  name: z.string().optional(),
});

Здесь поле name может отсутствовать, но остальные поля объекта остаются обязательными.

В partial-схеме:

Schema.partial();

объект целиком становится гибким по структуре.


Использование partial в PATCH-операциях

Частичная валидация наиболее естественно применяется в API-методах обновления:

const UpdateUserSchema = UserSchema.partial();

app.patch("/user", (req) => {
  const data = UpdateUserSchema.parse(req.body);
});

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


partial + pick и omit

Методы .pick() и .omit() часто комбинируются с partial-схемами.

Сначала выбор полей, затем частичность

const Base = UserSchema.pick({
  name: true,
  email: true,
}).partial();

В этом случае только выбранные поля становятся optional.

Сначала partial, затем pick

const Base = UserSchema.partial().pick({
  name: true,
});

Поведение зависит от порядка вызовов, так как каждый метод возвращает новую схему.


Частичная валидация и значения по умолчанию

При наличии .default() поведение меняется:

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

Если поле role отсутствует во входных данных, оно не будет автоматически заменено на "user" при валидации. Default применяется только если поле присутствует в схеме и не отсутствует как optional без дополнительной обработки.


Strict, passthrough и partial

Поведение строгой проверки влияет на частичные схемы:

UserSchema.partial().strict();
  • strict() запрещает неизвестные поля
  • partial() делает известные поля необязательными

Комбинация часто используется для API, где структура гибкая, но расширение недопустимо.


Partial и массивы

.partial() не влияет на элементы массивов:

const Schema = z.object({
  tags: z.array(z.object({
    id: z.number(),
    label: z.string(),
  })),
}).partial();

Поле tags становится optional, но элементы массива остаются строго типизированными.


Частичная валидация и union-типы

При использовании union-типов:

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

.partial() не применяется напрямую ко всему union, требуется оборачивать каждый вариант отдельно, иначе поведение остаётся неизменным.


Типизация TypeScript и partial схемы

Zod синхронизирует runtime-валидацию с TypeScript типами:

type PartialUser = z.infer<typeof PartialUserSchema>;

Результирующий тип автоматически отражает optional-поля, исключая необходимость ручного определения Partial.


Ошибки при частичной валидации

Наиболее частые источники ошибок:

  • ожидание автоматического заполнения default-значений
  • попытка использовать partial для глубоких структур без deepPartial
  • неверное предположение о влиянии partial на массивы
  • конфликт strict-режима с отсутствием полей

Практические сценарии применения

Частичная валидация используется в следующих областях:

  • редактирование профиля пользователя
  • обновление настроек
  • PATCH REST API
  • частичные формы в UI
  • синхронизация данных с клиентом
  • миграция схем без нарушения обратной совместимости

Комбинирование partial с трансформациями

В сложных схемах partial часто сочетается с .transform():

const Schema = UserSchema.partial().transform((data) => ({
  ...data,
  updatedAt: Date.now(),
}));

В этом случае частичные данные дополняются вычисляемыми полями, сохраняя гибкость входа и стабильность выходной структуры.