Большая часть входных данных в JavaScript-приложениях поступает из
внешних источников: HTTP-запросы, формы, WebSocket-сообщения,
localStorage, сторонние API. На этапе выполнения тип этих данных заранее
не определён. Даже если в коде ожидается объект определённой формы,
фактически приходит значение типа unknown — строка, число,
null, произвольный объект или структура с неожиданными
полями.
TypeScript не решает эту проблему автоматически, поскольку проверка типов существует только на этапе компиляции. В рантайме любое значение требует валидации. Именно в этом контексте Zod выступает как инструмент строгого описания и проверки структуры данных.
unknown
как базовая точка входаВ TypeScript тип unknown используется как безопасная
альтернатива any. Любое значение можно привести к
unknown, но дальнейшая работа с ним невозможна без явной
проверки.
Zod строит свою модель вокруг этого принципа: входные данные считаются неизвестными, пока не пройдут через схему.
import { z } from "zod";
const schema = z.string();
const value: unknown = JSON.parse('"hello"');
const result = schema.parse(value);
Здесь value рассматривается как полностью
неопределённый, и только parse устанавливает его
соответствие ожидаемому типу.
Работа с неизвестными данными в Zod обычно строится вокруг трёх механизмов:
parsesafeParsepreprocessМетод parse используется, когда нарушение схемы
считается исключительной ситуацией.
const schema = z.object({
id: z.number(),
});
schema.parse(JSON.parse('{"id": 1}'));
При несоответствии структуры выбрасывается ZodError,
содержащий подробную информацию о проблеме.
При работе с внешними источниками данных часто требуется избежать
исключений. В таких случаях применяется safeParse.
const schema = z.object({
id: z.number(),
});
const result = schema.safeParse(JSON.parse('{"id": "wrong"}'));
if (!result.success) {
result.error.issues;
} else {
result.data;
}
safeParse возвращает объект с двумя возможными
состояниями: успех или ошибка, что делает его удобным для обработки
неопределённых входов без прерывания потока выполнения.
Zod предоставляет два специальных типа для работы с полностью неизвестными данными:
z.unknown() — строгий вариант неизвестного
значенияz.any() — полностью отключает проверкуconst unknownSchema = z.unknown();
const anySchema = z.any();
Различие между ними принципиальное. unknown требует
дальнейшей проверки, тогда как any фактически исключает
валидацию.
В контексте работы с внешними данными z.unknown()
используется как промежуточный этап перед уточнением структуры.
Одна из ключевых моделей работы с неизвестными данными — постепенное уточнение структуры.
const base = z.unknown();
const objectSchema = z.object({
name: z.string(),
});
const parsed = objectSchema.parse(base.parse(JSON.parse('{"name":"A"}')));
На практике промежуточный unknown этап часто опускается,
но концептуально он отражает поток данных: от неопределённого состояния
к строго типизированному.
JSON является основным источником данных неопределённого типа. Любой
результат JSON.parse имеет тип any, но по сути
представляет собой unknown.
const raw = JSON.parse(input);
const schema = z.object({
userId: z.number(),
active: z.boolean(),
});
const validated = schema.safeParse(raw);
Проблема JSON заключается в отсутствии гарантий структуры: поля могут отсутствовать, иметь неправильный тип или содержать дополнительные значения.
Когда данные приходят в нестабильном формате, например строка вместо
числа, применяется z.preprocess.
const schema = z.object({
id: z.preprocess((val) => Number(val), z.number()),
});
Функция preprocess получает unknown и преобразует его
перед валидацией. Это особенно важно при работе с query-параметрами URL
или формами HTML.
Типичный случай:
"123" → 123undefined → NaN (с последующей ошибкой
валидации)Zod предоставляет более высокоуровневый механизм —
z.coerce, который автоматически приводит значения к нужному
типу.
const schema = z.object({
id: z.coerce.number(),
});
В этом случае строки, содержащие числа, автоматически приводятся к
number, если это возможно.
coerce уменьшает необходимость ручной обработки
unknown, но сохраняет контроль над финальной
валидацией.
В реальных API часто встречаются структуры, где часть полей известна, а часть — нет.
const schema = z.object({
id: z.number(),
}).passthrough();
Метод passthrough позволяет сохранять неизвестные поля
без валидации. Альтернативой является strict, который,
наоборот, запрещает любые лишние ключи.
const strictSchema = z.object({
id: z.number(),
}).strict();
Таким образом, поведение при встрече с unknown-ключами контролируется явно.
Когда структура объекта неизвестна полностью, но известен тип
значений, используется z.record.
const schema = z.record(z.number());
Это означает, что ключи могут быть любыми строками, а значения обязаны быть числами.
Такой подход часто применяется для:
Неизвестные данные часто имеют несколько возможных форм. Для этого
используется z.union.
const schema = z.union([
z.object({ type: z.literal("A"), value: z.string() }),
z.object({ type: z.literal("B"), value: z.number() }),
]);
При работе с внешними источниками union становится механизмом безопасной дискриминации структуры.
Если в данных присутствует поле-дискриминатор, используется
discriminatedUnion, позволяющий эффективно определять
структуру.
const schema = z.discriminatedUnion("type", [
z.object({ type: z.literal("user"), name: z.string() }),
z.object({ type: z.literal("admin"), level: z.number() }),
]);
Это снижает количество проверок и делает разбор неизвестных данных детерминированным.
При несоответствии данных схеме Zod возвращает
ZodError.
const result = z.string().safeParse(123);
Объект ошибки содержит:
Дополнительные методы:
error.flatten();
error.format();
flatten группирует ошибки по полям, упрощая обработку
вложенных структур.
При работе с API часто встречаются вложенные объекты:
const schema = z.object({
user: z.object({
profile: z.object({
age: z.number(),
}),
}),
});
Даже если верхний уровень частично известен, внутренние поля остаются потенциально unknown до завершения полной проверки.
Zod последовательно проходит по структуре, превращая каждый уровень из неизвестного состояния в строго типизированный.
Иногда входные данные представляют собой полностью неопределённые структуры.
const schema = z.object({
data: z.unknown(),
});
Здесь unknown выступает как явный маркер отсутствия
информации о содержимом. Дальнейшая работа с data требует
дополнительной валидации через вложенные схемы.
Для сложных случаев применяется refine, который
позволяет добавлять произвольную проверку после базовой валидации.
const schema = z.string().refine((val) => val.length > 3);
При работе с неизвестными данными refine часто
используется как финальный слой проверки после приведения типа.
Типичный путь данных через Zod можно описать как последовательность трансформаций:
unknown входpreprocess,
coerce)object, union,
record)refineКаждый этап уменьшает степень неопределённости и приближает данные к предсказуемой структуре.
JavaScript не предоставляет встроенного механизма описания контрактов данных. Любое значение потенциально является unknown, пока не проверено. Zod формализует этот процесс через декларативные схемы, превращая неопределённые входы в строго структурированные объекты без потери информации о возможных ошибках и отклонениях структуры.