Обработка ошибок в Zod строится вокруг объекта ZodError,
который содержит детализированную информацию о каждом нарушении схемы в
процессе парсинга данных. В отличие от простых строковых сообщений,
здесь формируется структурированное представление, позволяющее точно
определить источник проблемы, путь до поля и тип нарушения.
При неуспешной валидации Zod возвращает экземпляр
ZodError, внутри которого находится массив
issues. Каждый элемент описывает отдельную ошибку:
path — путь к полю в структуре данныхmessage — текстовое описание ошибкиcode — тип ошибки (например, invalid_type,
too_small, custom)Пример структуры:
import { z } from "zod";
const schema = z.object({
age: z.number(),
});
try {
schema.parse({ age: "not a number" });
} catch (e) {
console.log(e);
}
Результирующий ZodError будет содержать issue вида:
{
path: ["age"],
message: "Expected number, received string",
code: "invalid_type"
}
Такой формат позволяет легко строить пользовательские интерфейсы ошибок и API-ответы без дополнительного парсинга строк.
Zod предоставляет механизм централизованного управления сообщениями
об ошибках через z.setErrorMap.
errorMap — функция, которая получает контекст ошибки и
возвращает объект с сообщением:
import { z } from "zod";
z.setErrorMap((issue, ctx) => {
if (issue.code === "invalid_type") {
return { message: "Некорректный тип данных" };
}
return { message: ctx.defaultError };
});
Контекст ctx содержит:
defaultError — стандартное сообщение Zoddata — значение, которое не прошло проверкуpath — путь до поляТакой подход позволяет централизованно переопределять текст ошибок без изменения схем.
Помимо глобального механизма, сообщения можно задавать прямо в месте определения схемы:
const schema = z.object({
email: z.string({
required_error: "Email обязателен",
invalid_type_error: "Email должен быть строкой",
}),
});
Для базовых типов также поддерживаются встроенные методы:
const age = z.number().min(18, { message: "Возраст должен быть не менее 18" });
Это позволяет комбинировать глобальные и локальные стратегии обработки ошибок.
Методы refine и superRefine позволяют
создавать полностью кастомные правила валидации.
Используется для простых проверок:
const schema = z.string().refine((val) => val.startsWith("A"), {
message: "Строка должна начинаться с A",
});
Позволяет формировать сложные ошибки с точным указанием пути:
const schema = z.object({
password: z.string(),
confirmPassword: z.string(),
}).superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) {
ctx.addIssue({
path: ["confirmPassword"],
code: z.ZodIssueCode.custom,
message: "Пароли не совпадают",
});
}
});
superRefine особенно важен при проверке взаимосвязанных
полей.
Zod предоставляет два удобных метода преобразования ошибок в более пригодный для UI вид.
Преобразует ошибки в плоскую структуру:
const result = schema.safeParse(data);
if (!result.success) {
console.log(result.error.flatten());
}
Выход:
{
formErrors: [],
fieldErrors: {
age: ["Expected number, received string"]
}
}
Создаёт вложенную структуру, повторяющую схему данных:
result.error.format();
Пример результата:
{
age: {
_errors: ["Expected number, received string"]
}
}
Разница между ними заключается в способе представления:
flatten удобен для форм, format — для сложных
вложенных структур.
Для интеграции с API часто требуется привести ошибки к единому формату.
Пример преобразования:
function toApiErrors(error: z.ZodError) {
return error.issues.map((issue) => ({
field: issue.path.join("."),
message: issue.message,
type: issue.code,
}));
}
Результат удобно использовать в REST или GraphQL ответах.
Zod не содержит встроенной системы локализации, однако
errorMap позволяет реализовать её вручную:
const messages = {
ru: {
invalid_type: "Неверный тип данных",
too_small: "Слишком маленькое значение",
},
en: {
invalid_type: "Invalid type",
too_small: "Value is too small",
},
};
z.setErrorMap((issue, ctx) => {
const lang = "ru";
const msg = messages[lang][issue.code];
return {
message: msg || ctx.defaultError,
};
});
Такой подход масштабируется на любое количество языков без изменения схем.
Методы parse и safeParse определяют
поведение обработки ошибок:
parse — выбрасывает исключение
ZodErrorsafeParse — возвращает объект
{ success, data, error }Пример безопасного подхода:
const result = schema.safeParse(input);
if (!result.success) {
const errors = result.error.issues;
}
Это позволяет централизованно управлять потоком выполнения без try/catch.
Каждая ошибка имеет код, который определяет её тип:
invalid_typetoo_smalltoo_bigcustominvalid_literalunrecognized_keysИспользование кода позволяет строить условную обработку:
z.setErrorMap((issue, ctx) => {
switch (issue.code) {
case "too_small":
return { message: "Значение ниже допустимого" };
case "invalid_type":
return { message: "Неверный тип" };
default:
return { message: ctx.defaultError };
}
});
В прикладных системах часто используется единый слой обработки
ошибок, который принимает ZodError и преобразует его в
формат доменной модели.
Пример промежуточного слоя:
function handleValidationError(err: z.ZodError) {
return {
status: "validation_error",
fields: err.issues.reduce((acc, issue) => {
const key = issue.path.join(".");
acc[key] = issue.message;
return acc;
}, {} as Record<string, string>),
};
}
Такой слой отделяет валидацию от бизнес-логики и упрощает поддержку API.
При совместном использовании кастомных проверок и глобальных сообщений важно учитывать приоритет:
refine/superRefine имеет приоритетerrorMapctx.defaultErrorПример:
const schema = z.string().refine((v) => v.length > 3, {
message: "Слишком короткая строка",
});
В этом случае errorMap не переопределит заданное
сообщение, если оно уже явно указано.
При валидации вложенных объектов Zod собирает ошибки рекурсивно:
const schema = z.object({
user: z.object({
name: z.string(),
age: z.number(),
}),
});
Ошибки будут иметь путь:
["user", "name"]
["user", "age"]
Это позволяет строить точечную визуализацию ошибок на уровне форм и интерфейсов.
В крупных проектах часто вводится единый стандарт обработки ошибок Zod:
errorMapZodErrorПример стандартизированного преобразования:
function normalizeZodError(error: z.ZodError) {
return {
code: "VALIDATION_ERROR",
details: error.issues.map((i) => ({
path: i.path,
message: i.message,
type: i.code,
})),
};
}
Такая модель упрощает интеграцию с фронтендом и микросервисами.
При использовании z.union ошибки могут агрегироваться из
нескольких веток:
const schema = z.union([
z.string(),
z.number(),
]);
Если значение не подходит ни под одну ветку, Zod возвращает набор
ошибок, отражающих попытки каждой схемы. Это важно учитывать при
кастомной обработке, так как структура issues становится
более сложной и содержит вложенные причины отклонения.