В библиотеке Zod работа с частичными схемами строится вокруг преобразования строго описанных структур в их ослабленные варианты, где часть полей становится необязательной, но при этом сохраняется типовая целостность и контроль данных на уровне TypeScript.
Основной механизм частичных схем реализуется через метод
.partial(), применяемый к объектным схемам.
import { z } from "zod";
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string(),
age: z.number()
});
const PartialUserSchema = UserSchema.partial();
Результатом является новая схема, в которой каждое поле исходного объекта автоматически преобразуется в опциональное:
// эквивалентная структура
{
id?: string;
name?: string;
email?: string;
age?: number;
}
Важно, что partial() не удаляет ограничения типов, а
лишь изменяет статус обязательности полей, сохраняя валидаторы каждого
поля.
Метод .partial() работает только на первом уровне
вложенности. Вложенные объекты не становятся частичными
автоматически:
const Schema = z.object({
profile: z.object({
bio: z.string(),
website: z.string()
})
});
const PartialSchema = Schema.partial();
Результат:
{
profile?: {
bio: string;
website: string;
}
}
Внутренний объект profile остаётся полностью
строгим.
Для сценариев, где требуется рекурсивное ослабление структуры, используется глубокая частичность. В стандартной практике Zod это достигается через комбинацию утилит или пользовательских рекурсивных типов, поскольку поведение может зависеть от версии библиотеки.
Типовой подход через TypeScript:
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
Использование вместе с выводом типов Zod:
type User = z.infer<typeof UserSchema>;
type PartialUser = DeepPartial<User>;
Такой подход отделяет типовую трансформацию от runtime-схемы, сохраняя контроль на уровне компиляции.
Zod предоставляет два основных уровня типизации:
z.infer<typeof Schema> — тип результата после
валидацииz.input<typeof Schema> — тип входных данных до
трансформацийПри использовании .partial() поведение типов изменяется
автоматически:
type User = z.infer<typeof UserSchema>;
type PartialUser = z.infer<typeof PartialUserSchema>;
PartialUser эквивалентен
Partial<User> на уровне TypeScript, но с сохранением
оригинальных валидаторов в runtime.
Часто возникает путаница между optional() и
partial():
Применяется к конкретному полю:
z.string().optional();
Применяется ко всей объектной схеме:
z.object({...}).partial();
Ключевое различие:
optional() влияет на одно полеpartial() трансформирует структуру целикомЧастичные схемы широко используются для PATCH-операций, где требуется обновление только части сущности:
const UpdateUserSchema = UserSchema.partial();
function updateUser(data: z.infer<typeof UpdateUserSchema>) {
// data может содержать любое подмножество полей User
}
Такой подход исключает необходимость передачи полной модели и снижает связанность API.
Частичные схемы часто комбинируются с ограничением полей:
const NameEmailSchema = UserSchema.pick({
name: true,
email: true
}).partial();
Результат: оба поля становятся необязательными, но остальные полностью исключены.
const WithoutIdPartial = UserSchema.omit({
id: true
}).partial();
Здесь частичность применяется только к оставшимся полям.
При использовании .extend() частичность применяется
после композиции:
const Base = z.object({
id: z.string()
});
const Extended = Base.extend({
name: z.string()
}).partial();
Все поля итоговой схемы становятся опциональными.
Частичные схемы влияют только на наличие поля, но не подменяют значение по умолчанию:
const Schema = z.object({
name: z.string().default("anon")
}).partial();
В этом случае:
name допустимоdefault() применяется только если поле присутствует
или обрабатывается через parseВажное различие:
undefined как значениеZod различает эти состояния при валидации.
Частичные схемы часто используются вместе с preprocess,
когда входные данные нормализуются перед проверкой:
const Schema = z.object({
name: z.string()
}).partial().preprocess((val) => {
return val ?? {};
}, z.any());
Такой подход позволяет безопасно обрабатывать неполные структуры без ошибок парсинга.
Поверхностная частичность часто приводит к ожиданию глубокой модификации, что требует дополнительной обработки.
При объединении схем порядок операций влияет на итоговую типизацию:
.extend().partial() — всё опционально.partial().extend() — новые поля могут остаться
обязательнымиz.input может оставаться более строгим, чем
z.output, особенно при трансформациях и
default-значениях.
При работе с объединениями частичность применяется к каждому варианту отдельно:
const Schema = z.union([
z.object({ type: z.literal("a"), value: z.string() }),
z.object({ type: z.literal("b"), count: z.number() })
]).partial();
Каждый объект внутри union становится частичным независимо.
Для деревьев и графов частичность требует особого подхода, поскольку
стандартный .partial() не обрабатывает рекурсию:
const Node: z.ZodType<any> = z.lazy(() =>
z.object({
value: z.string(),
children: z.array(Node)
})
);
Применение частичности к таким схемам требует либо ручной трансформации, либо обобщённых типов.
Частичные схемы в Zod формируют слой адаптации между строгими моделями данных и гибкими структурами ввода. Они позволяют: