Валидация данных с автоматической типизацией с помощью zod

Read on English

При разработке часто возникает необходимость в валидации каких-либо данных. Например, это могут быть внешние данные от клиента (в случае, если мы работаем над API), или сообщения из RabbitMQ. 

И тут возникает проблема. Что делать, если мы хотим

  1. Валидировать без описания горы различных if, instanceof, typeof и прочего.
  2. Иметь TypeScript типы для представления нужных нам данных.
  3. Чтобы нам не приходилось одновременно поддерживать в актуальном состоянии и типы и код для валидации.

Для решения подобной задачи, можем воспользоваться библиотекой zod. Работу с ней мы и рассмотрим в этой статье.

Установка

Перед началом работы установим библиотеку:

$ npm install zod # or via yarn $ yarn add zod

Начало работы

Давайте начнём с небольшого практического примера. 

Допустим, у нас есть тип “Пользователь” со следующими полями:

  1. id типа number;
  2. email типа string;
  3. name типа string;
  4. И 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: '[email protected]', } const parsedUser = User.parse(invalidUser)

После запуска такого кода, мы получим ошибку, так как в объекте пользователя не хватает не хватает поля name:

Аналогично, zod скажет об ошибке, если поле неправильного типа данных:

const invalidUser = { id: '9', email: '[email protected]', } 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: '[email protected]', 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. Мы рассмотрели как эта библиотека помогает с валидацией данных и генерацией типов, чтобы вы были уверены в своих типах не только во время компиляции, но и во время выполнения кода.

С полным списком её возможностей можете ознакомиться в официальной документации.