Валидация объектов в экосистеме JavaScript обычно строится вокруг декларативного описания правил, применяемых к свойствам классов. Подход, реализованный в class-validator, основан на использовании декораторов и метаданных, что позволяет отделить структуру данных от логики проверки.
Объект рассматривается как набор полей, каждое из которых может иметь собственный набор ограничений. При этом сама сущность объекта остаётся неизменной, а правила валидации описываются отдельно.
В основе лежит преобразование класса в набор метаданных, где каждое свойство связывается с набором валидаторов.
Типичный процесс включает следующие этапы:
import { validate } from "class-validator";
class User {
name: string;
age: number;
}
const user = new User();
user.name = "Alex";
user.age = 25;
const errors = await validate(user);
В таком виде объект не содержит ограничений, поэтому результат проверки будет всегда успешным. Для активации правил используются декораторы.
Основная сила системы заключается в аннотациях полей. Каждое правило добавляется через декоратор, который регистрируется в метаданных класса.
import { IsString, IsInt, Min } from "class-validator";
class User {
@IsString()
name: string;
@IsInt()
@Min(18)
age: number;
}
В данном случае:
@IsString() фиксирует тип строки@IsInt() ограничивает значение целым числом@Min(18) задаёт нижнюю границу допустимого
диапазонаПри выполнении проверки библиотека проходит по всем зарегистрированным метаданным и применяет соответствующие функции.
Результат работы представляет собой массив объектов ошибок. Каждый элемент содержит информацию о конкретном нарушении:
[
{
property: "age",
constraints: {
min: "age must not be less than 18"
}
}
]
Такая структура позволяет строить сложные механизмы обработки ошибок на уровне бизнес-логики или API.
Объектная модель поддерживает иерархическую структуру данных. Вложенные классы валидируются при наличии соответствующих декораторов.
import { ValidateNested } from "class-validator";
import { Type } from "class-transformer";
class Address {
city: string;
}
class User {
@ValidateNested()
@Type(() => Address)
address: Address;
}
Без явного указания преобразования вложенные структуры не проходят полноценную валидацию, так как данные остаются обычными объектами без связи с классом.
Часто входящие данные поступают в виде JSON, где типы не соответствуют ожидаемым. В таких случаях используется связка с преобразованием типов.
import { plainToInstance } from "class-transformer";
const payload = {
name: "Alex",
age: "25"
};
const user = plainToInstance(User, payload);
После преобразования становится возможным применение строгих правил, включая числовые и логические ограничения.
Механизм позволяет задавать условия, при которых правило применяется. Это реализуется через функции-валидаторы или опции декораторов.
import { ValidateIf, IsString } from "class-validator";
class User {
@ValidateIf(o => o.isActive)
@IsString()
nickname: string;
isActive: boolean;
}
В данном случае проверка поля зависит от состояния другого свойства объекта.
Расширение системы осуществляется через создание пользовательских правил. Валидатор реализует интерфейс проверки и может быть использован как декоратор.
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments
} from "class-validator";
@ValidatorConstraint({ name: "isEven", async: false })
class IsEvenConstraint implements ValidatorConstraintInterface {
validate(value: number) {
return value % 2 === 0;
}
defaultMessage(args: ValidationArguments) {
return "значение должно быть чётным";
}
}
Подключение осуществляется через декоратор:
import { Validate } from "class-validator";
class NumberModel {
@Validate(IsEvenConstraint)
value: number;
}
Массивы рассматриваются как коллекции элементов, каждый из которых может быть валидирован отдельно.
import { IsString, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
class Item {
@IsString()
title: string;
}
class Order {
@ValidateNested({ each: true })
@Type(() => Item)
items: Item[];
}
Флаг each: true активирует проверку каждого элемента
массива.
Поддерживается разделение правил на группы, что позволяет применять разные наборы ограничений к одному объекту в разных контекстах.
import { IsString } from "class-validator";
class User {
@IsString({ groups: ["create"] })
name: string;
@IsString({ groups: ["update"] })
id: string;
}
При вызове проверки можно указать активную группу правил, что влияет на итоговый результат.
Некоторые ограничения требуют обращения к внешним источникам, например к базе данных. В таких случаях используется асинхронный режим.
@ValidatorConstraint({ async: true })
class IsEmailUnique implements ValidatorConstraintInterface {
async validate(email: string) {
return await checkEmailInDatabase(email);
}
}
Асинхронные проверки интегрируются в общий процесс и влияют на итоговый результат так же, как синхронные.
Валидация может применяться не ко всему объекту, а к отдельным полям. Это используется в сценариях частичного обновления данных.
await validate(user, { skipMissingProperties: true });
В таком режиме отсутствующие поля не участвуют в проверке, что позволяет обрабатывать частичные структуры без ошибок, связанных с неполнотой данных.