Интеграция с class-transformer

Библиотеки class-validator и class-transformer образуют типичный дуэт в экосистеме работы с DTO-объектами в JavaScript/TypeScript-проектах, особенно в серверных приложениях на основе NestJS или аналогичных архитектур.

class-validator отвечает за проверку данных через декораторы валидации, а class-transformer обеспечивает преобразование обычных объектов (plain objects) в экземпляры классов и обратно. Совместное использование этих библиотек решает ключевую проблему: валидация «сырых» входных данных невозможна без предварительного приведения их к структуре классов.


Базовый принцип взаимодействия

Основная идея интеграции заключается в следующем:

  1. Входные данные приходят как обычный объект (JSON).
  2. class-transformer преобразует его в экземпляр класса.
  3. class-validator выполняет проверку на основе декораторов.
  4. При необходимости объект используется дальше как типизированная сущность.

Ключевой момент: валидация работает только с экземплярами классов, а не с plain object.


Преобразование объектов через plainToInstance

Главный инструмент интеграции — функция plainToInstance.

import { plainToInstance } from 'class-transformer';

class User {
  name: string;
  age: number;
}

const plainUser = {
  name: 'Alex',
  age: 25,
};

const userInstance = plainToInstance(User, plainUser);

После преобразования userInstance становится полноценным экземпляром класса User, что позволяет применять декораторы class-validator.


Подключение class-validator к классу

Валидация строится на декораторах, описывающих ограничения для полей.

import { IsString, IsInt, Min, Max } from 'class-validator';

class User {
  @IsString()
  name: string;

  @IsInt()
  @Min(0)
  @Max(120)
  age: number;
}

Без предварительного преобразования через class-transformer подобная схема не будет работать корректно при получении данных извне.


Полный цикл трансформации и валидации

На практике интеграция выглядит как последовательность двух операций: трансформация и проверка.

import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';

class User {
  @IsString()
  name: string;

  @IsInt()
  age: number;
}

const input = {
  name: 'John',
  age: 30,
};

const instance = plainToInstance(User, input);

validate(instance).then(errors => {
  if (errors.length > 0) {
    console.log('Ошибки валидации');
  } else {
    console.log('Данные корректны');
  }
});

Опция enableImplicitConversion

Одной из ключевых точек интеграции является автоматическое приведение типов.

import { plainToInstance } from 'class-transformer';

const instance = plainToInstance(User, input, {
  enableImplicitConversion: true,
});

Поведение опции

  • строка "25" может быть преобразована в number
  • "true" может быть преобразовано в boolean
  • массивы и вложенные структуры также корректно трансформируются

Важно: без этой опции class-validator может получать значения неправильного типа, что приведёт к ложным ошибкам.


Валидация вложенных объектов

Одной из сильных сторон связки является работа с вложенными DTO.

import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';

class Address {
  @IsString()
  city: string;

  @IsString()
  street: string;
}

class User {
  @IsString()
  name: string;

  @ValidateNested()
  @Type(() => Address)
  address: Address;
}

Здесь критически важны два механизма:

  • @Type(() => Address) — сообщает class-transformer, как создавать вложенный объект
  • @ValidateNested() — активирует рекурсивную валидацию

Без @Type вложенный объект останется plain object, и валидация не выполнится корректно.


Работа с массивами объектов

При работе с коллекциями требуется комбинирование @Type и @ValidateNested({ each: true }).

class Role {
  @IsString()
  name: string;
}

class User {
  @IsString()
  username: string;

  @ValidateNested({ each: true })
  @Type(() => Role)
  roles: Role[];
}

Поведение:

  • каждый элемент массива преобразуется в экземпляр Role
  • каждый элемент проходит собственную валидацию

Transform-декораторы как часть интеграции

class-transformer предоставляет механизм модификации значений до валидации.

@Transform

import { Transform } from 'class-transformer';

class User {
  @Transform(({ value }) => value.trim())
  @IsString()
  name: string;
}

Значение сначала преобразуется, затем проверяется class-validator.


Порядок выполнения трансформаций и валидации

Критически важный аспект архитектуры:

  1. Plain object поступает в систему

  2. Выполняется class-transformer

    • преобразование типов
    • применение @Transform
    • создание вложенных объектов
  3. Выполняется class-validator

    • проверка всех декораторов
    • рекурсивная валидация

Ошибка в этом порядке приводит к некорректным результатам валидации.


Исключение лишних полей через expose/exclude

При интеграции часто требуется фильтрация входных данных.

import { Expose, Exclude } from 'class-transformer';

class User {
  @Expose()
  name: string;

  @Expose()
  age: number;

  @Exclude()
  password: string;
}

Использование:

const instance = plainToInstance(User, input, {
  excludeExtraneousValues: true,
});

Поведение

  • поля без @Expose() удаляются
  • лишние свойства игнорируются
  • модель становится строго контролируемой

Сценарий серверной обработки DTO

Типовой pipeline:

const dto = plainToInstance(CreateUserDto, request.body, {
  enableImplicitConversion: true,
  excludeExtraneousValues: true,
});

const errors = await validate(dto);

if (errors.length > 0) {
  throw new Error('Validation failed');
}

Здесь объединяются:

  • очистка входных данных
  • приведение типов
  • строгая валидация

Важные ограничения интеграции

1. Отсутствие автоматической трансформации

class-validator не вызывает class-transformer автоматически. Преобразование всегда должно выполняться явно.


2. Потеря типов при неправильной конфигурации

Без @Type вложенные структуры остаются plain objects, что делает @ValidateNested неэффективным.


3. Проблемы с примитивными типами

Без enableImplicitConversion возможны ошибки:

  • "123" не проходит @IsInt()
  • "false" не распознаётся как boolean

Расширенные сценарии интеграции

Условная валидация после трансформации

class User {
  @Transform(({ value }) => value?.toLowerCase())
  @IsString()
  email: string;
}

Сначала выполняется нормализация, затем проверка формата.


Динамическая типизация вложенных структур

class A {
  @Type(() => B)
  @ValidateNested()
  data: B;
}

class B {
  @IsString()
  value: string;
}

При изменении входных данных структура остаётся валидируемой благодаря Type.


Использование с массивами смешанных типов

При сложных структурах возможно комбинирование:

class Event {
  @ValidateNested({ each: true })
  @Type(() => Participant)
  participants: Participant[];
}

Каждый элемент массива проходит независимую трансформацию и проверку.


Архитектурная роль связки в приложениях

Связка class-transformer и class-validator формирует слой DTO, который выполняет функции:

  • нормализация входных данных
  • защита от некорректных структур
  • приведение типов к доменной модели
  • контроль внешних API контрактов

Эта комбинация становится промежуточным слоем между транспортом данных (HTTP, RPC, WebSocket) и бизнес-логикой, обеспечивая строгую изоляцию доменной модели от внешних форматов данных.