В системах, построенных на JavaScript и TypeScript, проверка типов на этапе компиляции является первой линией защиты от ошибок. TypeScript анализирует код до выполнения и гарантирует соответствие структур данных заявленным типам.
Пример:
type User = {
id: number;
name: string;
};
const user: User = {
id: 1,
name: "Alex",
};
На этапе компиляции TypeScript гарантирует, что объект соответствует
типу User. Однако этот механизм имеет фундаментальное
ограничение: он не контролирует данные, приходящие извне
программы.
const data = JSON.parse('{"id":"1","name":123}');
С точки зрения TypeScript после JSON.parse тип данных
становится any. Проверка исчезает, и ошибка может
проявиться только во время выполнения.
Валидация во время выполнения (runtime validation) решает проблему недоверия к внешним данным. Любой вход в систему считается потенциально некорректным.
Источники таких данных:
Без runtime-валидации система работает с предположениями, а не с гарантиями.
Zod представляет собой библиотеку, ориентированную на описание схем данных и их проверку во время выполнения с автоматическим выводом типов TypeScript.
Ключевая идея: одна схема = и валидация, и тип.
TypeScript описывает форму данных, но не может гарантировать их реальную структуру после получения извне.
type User = {
id: number;
name: string;
};
const response = await fetch("/api/user");
const data: User = await response.json();
TypeScript считает data корректным User, но
фактически сервер может вернуть:
{
"id": "not-a-number",
"name": true
}
Compile-time проверка здесь бесполезна, потому что:
fetch возвращает any-подобную
структуру;Runtime-валидация вводит дополнительный слой:
Zod объединяет:
import { z } from "zod";
const UserSchema = z.object({
id: z.number(),
name: z.string(),
});
type User = z.infer<typeof UserSchema>;
Здесь:
UserSchema работает в runtime;User — статический TypeScript-тип;type Product = {
id: number;
price: number;
};
async function getProduct(): Promise<Product> {
const res = await fetch("/product");
return res.json();
}
Проблема: res.json() не гарантирует структуру
Product.
import { z } from "zod";
const ProductSchema = z.object({
id: z.number(),
price: z.number(),
});
type Product = z.infer<typeof ProductSchema>;
async function getProduct(): Promise<Product> {
const res = await fetch("/product");
const json = await res.json();
return ProductSchema.parse(json);
}
Теперь:
Product соответствует реальной структуре;Runtime-валидация вводит важный принцип:
любые данные вне текущего процесса считаются недостоверными до проверки
Это включает:
Одна из ключевых концепций Zod — парсинг вместо кастинга типов.
const user = data as User;
Это не проверка, а обещание компилятору.
const user = UserSchema.parse(data);
Здесь происходит реальная проверка структуры.
Zod поддерживает разные режимы обработки данных:
UserSchema.parse(data);
UserSchema.safeParse(data);
Возвращает:
{
success: boolean;
data?: T;
error?: ZodError;
}
Использование runtime-валидации приводит к архитектурному разделению:
До появления схемных библиотек существовала проблема:
Zod решает это через:
const Schema = z.object({
id: z.number(),
});
type SchemaType = z.infer<typeof Schema>;
Runtime-валидация становится особенно важной при сложных структурах:
const OrderSchema = z.object({
id: z.string(),
items: z.array(
z.object({
productId: z.number(),
quantity: z.number().min(1),
})
),
});
Здесь TypeScript сам по себе не способен гарантировать корректность входных данных без runtime-проверки.
В Zod ошибки становятся структурированными:
Это делает возможной предсказуемую обработку ошибок.
Современные TypeScript-приложения строятся на двойной системе:
Их совместное использование создаёт замкнутую цепочку:
данные → runtime-валидация → типизированное использование → compile-time безопасность внутри системы