Валидация данных в Zod строится вокруг объекта ZodError,
который содержит массив issues. Каждая ошибка
представляется как отдельная запись с унифицированной структурой, где
ключевую роль играет поле path. Именно оно определяет,
является ошибка плоской или вложенной.
import { z } from "zod";
const schema = z.object({
user: z.object({
name: z.string(),
}),
});
schema.parse({ user: { name: 123 } });
Результат такой валидации формирует ошибку, внутри которой будет один
элемент issues, но с путём:
{
issues: [
{
path: ["user", "name"],
message: "Expected string, received number",
code: "invalid_type"
}
]
}
Плоская ошибка в Zod — это ситуация, когда каждый элемент
issue относится к одному конкретному полю без вложенной
структуры обработки.
Характерные признаки:
path содержит один уровень или пустой массивПример:
const schema = z.object({
name: z.string(),
age: z.number(),
});
schema.parse({ name: 123, age: "old" });
Результат:
[
{ path: ["name"], message: "Expected string, received number" },
{ path: ["age"], message: "Expected number, received string" }
]
Такая форма удобна для простого отображения списком или логирования, поскольку каждая ошибка — автономная единица.
Вложенность возникает при использовании z.object,
z.array, z.tuple и любых комбинированных
схем.
Ключевая концепция — path как путь в дереве данных.
Пример вложенности:
const schema = z.object({
user: z.object({
address: z.object({
city: z.string(),
}),
}),
});
Ошибка:
schema.parse({
user: {
address: {
city: 123,
},
},
});
Результат:
{
path: ["user", "address", "city"],
message: "Expected string, received number"
}
path всегда является массивом:
arrayobjectПример массива:
const schema = z.array(z.object({ name: z.string() }));
schema.parse([{ name: 123 }]);
Ошибка:
{
path: [0, "name"],
message: "Expected string, received number"
}
ZodError.issues — это плоский список, даже если данные
вложенные. Zod не хранит дерево ошибок в виде дерева — только список с
путями.
Структура:
type ZodIssue = {
path: (string | number)[];
message: string;
code: string;
};
Это означает:
pathОдни и те же данные могут требовать двух форм отображения:
Используется для:
Пример:
[
"user.name: invalid type",
"user.address.city: required"
]
Используется для:
Пример группировки:
{
user: {
name: "invalid type",
address: {
city: "required"
}
}
}
Zod предоставляет метод flatten() для преобразования
ошибок в более простой формат:
const result = schema.safeParse(data);
if (!result.success) {
const flat = result.error.flatten();
}
Структура результата:
{
formErrors: string[],
fieldErrors: {
user?: string[],
"user.name"?: string[]
}
}
Особенность:
formErrorsfieldErrorsМетод format() сохраняет структуру объекта и
раскладывает ошибки по тем же ключам, что и схема.
Пример:
const result = schema.safeParse(data);
if (!result.success) {
const formatted = result.error.format();
}
Результат:
{
user: {
_errors: [],
name: {
_errors: ["Expected string, received number"]
},
address: {
city: {
_errors: ["Expected string, received number"]
}
}
}
}
| Метод | Представление | Назначение |
|---|---|---|
| flatten() | плоское | обработка списков ошибок |
| format() | вложенное | UI-формы |
При использовании кастомной валидации через refine и
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: "custom",
});
}
});
Даже кастомная ошибка сохраняет структуру:
issuesПри использовании z.union() ошибки могут выглядеть как
вложенные альтернативные ветки.
const schema = z.union([
z.object({ type: z.literal("a"), value: z.string() }),
z.object({ type: z.literal("b"), count: z.number() }),
]);
Если вход не соответствует ни одному варианту:
issuesЭто создаёт эффект «псевдовложенности», но структура остаётся плоской.
Поскольку Zod хранит ошибки в плоском массиве, вложенность часто восстанавливается вручную:
function groupByPath(issues) {
const tree = {};
for (const issue of issues) {
let current = tree;
for (const key of issue.path) {
current[key] = current[key] || {};
current = current[key];
}
current._errors = current._errors || [];
current._errors.push(issue.message);
}
return tree;
}
Такая трансформация необходима при построении сложных UI-форм.
В реальных схемах path может содержать:
Пример:
["users", 3, "posts", 1, "title"]
Это означает цепочку доступа:
users[3].posts[1].title
Такая форма делает Zod-ошибки универсальными для любых структур данных, но требует дополнительной обработки на уровне отображения.