При разработке часто возникает необходимость в валидации каких-либо данных. Например, это могут быть внешние данные от клиента (в случае, если мы работаем над API), или сообщения из RabbitMQ.
И тут возникает проблема. Что делать, если мы хотим
- Валидировать без описания горы различных if, instanceof, typeof и прочего.
- Иметь TypeScript типы для представления нужных нам данных.
- Чтобы нам не приходилось одновременно поддерживать в актуальном состоянии и типы и код для валидации.
Для решения подобной задачи, можем воспользоваться библиотекой zod. Работу с ней мы и рассмотрим в этой статье.
Установка
Перед началом работы установим библиотеку:
$ npm install zod
# or via yarn
$ yarn add zod
Начало работы
Давайте начнём с небольшого практического примера.
Допустим, у нас есть тип “Пользователь” со следующими полями:
- idтипа- number;
- emailтипа- string;
- nameтипа- string;
- И githubIdтипаstring(только для пользователей, которые привязали GitHub аккаунт, поэтому необязательное).
И мы хотим проверить, подходит ли переданный нам объект под этом тип.
Для начала, нам необходимо описать этот тип с помощью средств zod:
/**
 * Import zod library
 */
import { z } from 'zod';
/**
 * Define our User model using zod
 */
const User = z.object({
    id: z.string(),
    email: z.string(),
    name: z.string(),
    githubId: z.string().optional(),
});
Теперь попробуем проверить, соответствует ли объект этой схеме:
const invalidUser = {
    id: 9,
    email: 'example@mail.com',
}
const parsedUser = User.parse(invalidUser)
После запуска такого кода, мы получим ошибку, так как в объекте пользователя не хватает не хватает поля name:
 
    
    Аналогично, zod скажет об ошибке, если поле неправильного типа данных:
const invalidUser = {
    id: '9',
    email: 'example@mail.com',
}
const parsedUser = User.parse(invalidUser)
Окей, а что с генерацией типов для TypeScript? Zod может предоставить нам их:
 
    
    Теперь этот тип мы можем использовать в нашем коде:
 
    
    Таким образом, описывая типы с помощью zod, мы получаем возможность легко валидировать данные в рантайме, а также использовать типы, чтобы узнавать об ошибках во время компиляции.
Мы рассмотрели один из самых простых кейсов использования zod, а сейчас рассмотрим что ещё может нам предложить эта библиотека.
Расширенная валидация строк и чисел
С помощью zod мы можем задать правила для длины, формата (url, email, uuid), минимальных и максимальных значений и другие свойства:
/**
 * Define our User model using zod
 */
const User = z.object({
    id: z.string().uuid(),
    email: z.string().email(),
    name: z.string().nonempty(),
    githubId: z.string().optional(),
});
Валидация массивов
Пусть у каждого пользователя будет массив проектов. Опишем это:
const Project = z.object({
    id: z.string().uuid(),
    name: z.string()
});
const User = z.object({
    id: z.string().uuid(),
    email: z.string().email(),
    name: z.string().nonempty(),
    githubId: z.string().optional(),
    projects: z.array(Project)
});
Проверим работу:
const invalidUser = {
    id: '694a8b3a-d782-44fe-ad35-4a63860ab742',
    name: 'John',
    email: 'example@mail.com',
    projects: [{name: 'Zod'}]
}
Видим ошибку, всё работает верно:
 
    
    Типы тоже на месте:
 
    
    Работа с объектами
Zod предоставляет много различных методов для работы с объектами: наследование, объединение, исключение полей (omit), выборка полей (pick) и другие.
const UserWithoutId = User.omit({id: true})
const PartialUser = User.partial(); // all fields are optional
const WithImage = z.object({image: z.string()});
const UserWithImage = User.merge(WithImage);
Конвертация TypeScript определений в схемы zod
С помощью утилиты ts-to-zod мы можем делать наоборот — генерировать zod-схему из определений типов в TypeScript.
Давайте преобразуем с помощью этой утилиты TypeScript интерфейс в схему zod:
/**
 * Workspace representation in DataBase
 */
export interface Workspace {
    /**
     * Workspace's id
     */
    _id: string;
    /**
     * Workspace's name
     */
    name: string;
    /**
     * Workspace's description
     */
    description?: string;
    /**
     * Workspace's image URL
     */
    image?: string;
    /**
     * Date when workspace was charged last time
     */
    lastChargeDate: Date;
}
Запускаем команду для генерации схемы:
 
    
    И смотрим результат:
// Generated by ts-to-zod
import { z } from "zod";
export const workspaceSchema = z.object({
  _id: z.string(),
  name: z.string(),
  description: z.string().optional(),
  image: z.string().optional(),
  lastChargeDate: z.date(),
});
Теперь можно пользоваться полученной схемой для проверки данных.
По ссылке можно ознакомиться и с другими полезными утилитами экосистемы zod.
Заключение
На этом мы закончим краткий обзор библиотеки zod. Мы рассмотрели как эта библиотека помогает с валидацией данных и генерацией типов, чтобы вы были уверены в своих типах не только во время компиляции, но и во время выполнения кода.
С полным списком её возможностей можете ознакомиться в официальной документации.