Валидация объектов

Валидация объектов в экосистеме JavaScript обычно строится вокруг декларативного описания правил, применяемых к свойствам классов. Подход, реализованный в class-validator, основан на использовании декораторов и метаданных, что позволяет отделить структуру данных от логики проверки.

Объект рассматривается как набор полей, каждое из которых может иметь собственный набор ограничений. При этом сама сущность объекта остаётся неизменной, а правила валидации описываются отдельно.

Базовый принцип работы с объектами

В основе лежит преобразование класса в набор метаданных, где каждое свойство связывается с набором валидаторов.

Типичный процесс включает следующие этапы:

  • объявление класса-DTO
  • добавление декораторов к полям
  • выполнение функции проверки объекта
  • получение результата в виде списка ошибок
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 });

В таком режиме отсутствующие поля не участвуют в проверке, что позволяет обрабатывать частичные структуры без ошибок, связанных с неполнотой данных.