Опциональное поле — это поле, которое может отсутствовать в объекте. В JavaScript подобные свойства часто используются в формах, DTO, API-ответах, конфигурациях и параметрах функций.
В Zod опциональность задаётся методом .optional().
import { z } from "zod";
const UserSchema = z.object({
name: z.string(),
age: z.number().optional(),
});
Поле age:
Допустимые значения:
UserSchema.parse({
name: "Alex",
});
UserSchema.parse({
name: "Alex",
age: 25,
});
Ошибка:
UserSchema.parse({
name: "Alex",
age: "25",
});
Результат:
Expected number, received string
.optional()Метод .optional() превращает схему:
z.string()
в:
z.string().optional()
что эквивалентно:
z.union([z.string(), z.undefined()])
То есть схема начинает принимать:
undefined.Zod автоматически выводит корректный тип.
const Schema = z.object({
title: z.string().optional(),
});
type SchemaType = z.infer<typeof Schema>;
Получится:
type SchemaType = {
title?: string | undefined;
};
Важно понимать:
title?: string
и
title: string | undefined
— не одно и то же.
{
title?: string;
}
Свойство может отсутствовать:
{}
undefined{
title: string | undefined;
}
Свойство обязательно должно существовать:
{
title: undefined
}
Zod учитывает эту разницу.
Nullable-поле — это поле, которое может содержать
null.
Для этого используется .nullable().
const UserSchema = z.object({
avatar: z.string().nullable(),
});
Допустимые значения:
UserSchema.parse({
avatar: "avatar.png",
});
UserSchema.parse({
avatar: null,
});
Ошибка:
UserSchema.parse({
avatar: undefined,
});
.nullable()z.string().nullable()
эквивалентно:
z.union([z.string(), z.null()])
Схема принимает:
null.const Schema = z.object({
avatar: z.string().nullable(),
});
type SchemaType = z.infer<typeof Schema>;
Результат:
type SchemaType = {
avatar: string | null;
};
Поле обязательно присутствует, но может быть null.
Это одна из важнейших тем при работе с Zod.
.optional()z.string().optional()
Разрешает:
undefined
или отсутствие поля.
const Schema = z.object({
name: z.string().optional(),
});
Допустимо:
{}
Допустимо:
{
name: undefined
}
.nullable()z.string().nullable()
Разрешает:
null
Но поле обязано существовать.
const Schema = z.object({
name: z.string().nullable(),
});
Допустимо:
{
name: null
}
Ошибка:
{}
Очень распространённая ситуация — поле может:
null;Для этого используется комбинация:
z.string().optional().nullable()
или:
z.string().nullable().optional()
Оба варианта работают одинаково.
const Schema = z.object({
description: z.string().optional().nullable(),
});
Допустимо:
{}
{
description: undefined
}
{
description: null
}
{
description: "Text"
}
type SchemaType = {
description?: string | null | undefined;
};
.nullish()В Zod существует сокращение:
.nullish()
Оно объединяет:
.optional().nullable()
const Schema = z.object({
bio: z.string().nullish(),
});
Эквивалент:
const Schema = z.object({
bio: z.string().optional().nullable(),
});
.nullish(){}
{
bio: undefined
}
{
bio: null
}
{
bio: "Developer"
}
Часто требуется сделать опциональными сразу все поля объекта.
Для этого используется .partial().
const UserSchema = z.object({
name: z.string(),
email: z.string(),
age: z.number(),
});
const PartialUserSchema = UserSchema.partial();
Теперь все поля опциональны.
Допустимо:
{}
{
name: "Alex"
}
{
email: "test@test.com"
}
type PartialUser = {
name?: string;
email?: string;
age?: number;
};
.partial() может принимать объект с настройками.
const UserSchema = z.object({
name: z.string(),
email: z.string(),
age: z.number(),
});
const UpdateSchema = UserSchema.partial({
age: true,
});
Только age станет опциональным.
Для вложенных объектов используется .deepPartial().
const Schema = z.object({
user: z.object({
profile: z.object({
avatar: z.string(),
}),
}),
});
const DeepSchema = Schema.deepPartial();
Теперь опциональными станут все вложенные поля.
Допустимо:
{}
{
user: {}
}
{
user: {
profile: {}
}
}
const Schema = z.array(z.string()).nullable();
Допустимо:
null
["a", "b"]
Ошибка:
[1, 2]
const Schema = z.array(
z.string().nullable()
);
Допустимо:
["a", null, "b"]
const Schema = z.array(
z.string().nullable()
).optional();
Допустимо:
undefined
null
для элементов массива:
["a", null]
.default()default() автоматически подставляет значение, если поле
отсутствует или равно undefined.
const Schema = z.object({
role: z.string().default("user"),
});
Schema.parse({});
Результат:
{
role: "user"
}
.default() автоматически делает поле optional.
То есть:
z.string().default("test")
уже не требует .optional().
nullconst Schema = z.object({
role: z.string().default("user"),
});
Schema.parse({
role: null,
});
Ошибка:
Expected string, received null
Потому что default() работает только с:
undefined.const Schema = z.object({
role: z.string().nullable().default("user"),
});
Допустимо:
{}
Результат:
{
role: "user"
}
Schema.parse({
role: null,
});
Результат:
{
role: null
}
null не заменяется значением по умолчанию.
При использовании .refine() важно учитывать возможное
отсутствие значения.
const Schema = z.string()
.optional()
.refine((value) => value.length > 3);
Проблема:
value
может быть undefined.
const Schema = z.string()
.optional()
.refine((value) => {
if (value === undefined) {
return true;
}
return value.length > 3;
});
const Schema = z.string()
.nullable()
.transform((value) => {
if (value === null) {
return "empty";
}
return value.toUpperCase();
});
preprocess позволяет преобразовывать входные данные до
валидации.
const Schema = z.preprocess(
(value) => {
if (value === null) {
return undefined;
}
return value;
},
z.string().optional()
);
Теперь:
Schema.parse(null);
пройдёт успешно.
Для PATCH-запросов поля почти всегда должны быть optional.
const UpdateUserSchema = z.object({
name: z.string().optional(),
email: z.string().email().optional(),
age: z.number().optional(),
});
Клиент может отправить только изменяемые поля.
Во многих ORM и SQL-базах:
NULL означает отсутствие значения;Это критически важно различать.
{
avatar: null
}
означает:
удалить значение
А:
{}
означает:
не изменять поле
const CreateUserSchema = z.object({
email: z.string().email(),
password: z.string(),
nickname: z.string().optional(),
avatar: z.string().nullable(),
});
const UpdateUserSchema = z.object({
email: z.string().email().optional(),
password: z.string().optional(),
nickname: z.string().optional(),
avatar: z.string().nullable().optional(),
});
z.string().optional()
не принимает:
null
z.string().nullable()
не принимает:
undefined
z.string().optional().default("test")
Достаточно:
z.string().default("test")
.refine(value => value.length > 5)
value может быть undefined.
.optional()Подходит для:
.nullable()Подходит для:
.nullish()Подходит, когда:
null и
undefined;| Схема | undefined | null | отсутствие поля |
|---|---|---|---|
z.string() |
❌ | ❌ | ❌ |
z.string().optional() |
✅ | ❌ | ✅ |
z.string().nullable() |
❌ | ✅ | ❌ |
z.string().nullish() |
✅ | ✅ | ✅ |