Исключение полей с @Exclude

В экосистеме TypeScript и JavaScript при работе с DTO (Data Transfer Objects) часто возникает необходимость контролировать не только валидацию входящих данных, но и их форму при преобразовании объектов между слоями приложения. Для этого используется связка двух библиотек: class-validator и class-transformer. Первая отвечает за проверку данных, вторая — за преобразование экземпляров классов в обычные объекты и обратно.

Декоратор @Exclude относится именно к class-transformer и используется для исключения свойств из процесса сериализации или десериализации. Его задача — управлять тем, какие поля попадут в итоговый объект после выполнения plainToInstance или instanceToPlain.


Базовый принцип работы @Exclude

При преобразовании объекта экземпляра класса в plain-объект (например, перед отправкой ответа API), class-transformer проходит по всем свойствам и применяет правила трансформации. Если свойство помечено @Exclude, оно исключается из результата.

import { Exclude } from 'class-transformer';

export class UserDto {
  id: number;

  email: string;

  @Exclude()
  password: string;
}

При преобразовании:

plainToInstance(UserDto, userEntity)

поле password будет удалено из итогового объекта.


Поведение по умолчанию

@Exclude работает только в контексте class-transformer и не влияет на исходный объект в памяти. Это означает:

  • исходный экземпляр класса остаётся неизменным;
  • исключение происходит только при трансформации;
  • поле всё ещё доступно внутри приложения до момента сериализации.

Использование с режимами трансформации

Декоратор поддерживает тонкую настройку поведения через параметры.

Исключение только при преобразовании в plain-объект

@Exclude({ toPlainOnly: true })
password: string;

В этом случае поле будет скрыто при отправке наружу, но останется доступным при создании объекта из plain-данных.


Исключение только при создании экземпляра класса

@Exclude({ toClassOnly: true })
internalFlag: boolean;

Такое поведение используется реже, но может быть полезно при фильтрации входных данных.


Полное исключение

@Exclude()
secretToken: string;

Поле исключается в обе стороны трансформации.


Взаимодействие с @Expose

@Exclude часто применяется совместно с @Expose, который выполняет обратную задачу — явно включает свойства в трансформацию.

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

export class ProductDto {
  @Expose()
  title: string;

  @Expose()
  price: number;

  @Exclude()
  internalCode: string;
}

При такой конфигурации можно построить модель, где видимыми считаются только явно отмеченные поля, а остальные игнорируются.


Глобальная стратегия исключений

class-transformer поддерживает режим, при котором можно задать поведение «по умолчанию»:

{
  excludeExtraneousValues: true
}

В этом режиме все поля, не помеченные @Expose, автоматически исключаются. Тогда @Exclude становится дополнительным инструментом точечной блокировки данных.


Применение в DTO-слое

В архитектуре приложений на NestJS и аналогичных фреймворках DTO-классы часто служат границей между слоями. Исключение полей позволяет:

  • скрывать чувствительные данные (пароли, токены);
  • формировать публичные API-модели;
  • отделять внутреннюю структуру базы данных от внешнего представления.
export class AccountDto {
  id: number;

  username: string;

  @Exclude()
  hashedPassword: string;

  @Exclude({ toPlainOnly: true })
  refreshToken: string;
}

Особенности взаимодействия с class-validator

Важно разграничивать роли библиотек:

  • class-validator отвечает за проверку данных (@IsString, @IsEmail, @MinLength);
  • class-transformer отвечает за преобразование и исключение полей (@Exclude, @Transform, @Expose).

@Exclude не выполняет валидацию и не влияет на неё напрямую. Однако в реальных приложениях оба слоя работают последовательно: сначала трансформация, затем проверка (или наоборот, в зависимости от конфигурации пайплайна).


Поведение при вложенных объектах

Исключение работает рекурсивно при условии, что вложенные объекты также являются экземплярами классов и имеют соответствующие декораторы.

export class ProfileDto {
  bio: string;

  @Exclude()
  internalNotes: string;
}

export class UserDto {
  name: string;

  profile: ProfileDto;
}

При корректной трансформации поле internalNotes внутри profile также будет исключено.


Ограничения и типичные ошибки

Отсутствие instance при трансформации

@Exclude работает только при использовании class-transformer. Если объект создаётся как plain JavaScript object без plainToInstance, декораторы не применяются.

Конфликт с ручным spread

const dto = {
  ...userInstance
};

В этом случае исключения не произойдёт, поскольку происходит прямое копирование свойств без участия class-transformer.

Ложное ожидание влияния на валидацию

@Exclude не предотвращает попадание данных в систему. Он лишь контролирует их представление после трансформации.


Практика построения безопасных DTO

Типичная модель использования включает разделение:

  • входные DTO (CreateUserDto, UpdateUserDto);
  • выходные DTO (UserResponseDto);
  • скрытие внутренних полей через @Exclude.
export class UserResponseDto {
  id: number;

  email: string;

  @Exclude()
  password: string;

  @Exclude()
  resetToken: string;
}

Такой подход снижает риск случайной утечки чувствительных данных при сериализации ответов API.