Включение полей с @Expose

В экосистеме работы с объектами данных в Node.js часто используются совместно две библиотеки: class-transformer и class-validator. Первая отвечает за преобразование plain-объектов в экземпляры классов и обратно, вторая — за проверку корректности данных через декораторы.

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


Механизм трансформации объектов

Перед тем как данные попадут под проверку валидатора, они часто проходят этап преобразования:

  • входящий JSON превращается в экземпляр класса;
  • лишние поля могут быть удалены;
  • типы приводятся к ожидаемым;
  • поля могут быть переименованы или переопределены.

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


Назначение @Expose

Декоратор @Expose используется для явного указания того, какие поля должны быть включены в процесс сериализации и десериализации.

Базовая идея

Если включён режим исключения лишних свойств, то только поля с @Expose будут попадать в объект:

  • всё, что не отмечено — игнорируется;
  • всё, что отмечено — участвует в трансформации.

Активация режима исключения лишних полей

Для того чтобы @Expose начал работать в строгом режиме, необходимо включить параметр:

  • excludeExtraneousValues: true

Он передаётся в функции преобразования:

plainToInstance(UserDto, data, {
  excludeExtraneousValues: true
});

Без этого параметра @Expose не ограничивает входящие данные, а используется только как дополнительная мета-информация.


Базовое использование @Expose

Определение DTO

import { Expose } from "class-transformer";

export class UserDto {
  @Expose()
  id: number;

  @Expose()
  email: string;

  password: string;
}

Поведение при трансформации

При включённом excludeExtraneousValues:

  • id и email будут включены;
  • password будет игнорирован.

Связка с валидацией

После трансформации данные передаются в систему валидации:

import { IsEmail } from "class-validator";

export class UserDto {
  @Expose()
  id: number;

  @Expose()
  @IsEmail()
  email: string;

  password: string;
}

Валидация работает только с теми полями, которые реально присутствуют в объекте после трансформации. Поэтому правильное использование @Expose напрямую влияет на поведение валидатора.


Исключение полей по умолчанию

Если @Expose не указан ни на одном поле, а режим excludeExtraneousValues включён, результат будет пустым объектом.

Это часто приводит к ошибкам:

  • кажется, что данные не приходят;
  • валидатор не срабатывает;
  • DTO оказывается «пустым».

Переименование свойств

@Expose позволяет не только включать поля, но и изменять их имя.

export class UserDto {
  @Expose({ name: "user_id" })
  id: number;

  @Expose({ name: "user_email" })
  email: string;
}

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

  • user_idid
  • user_emailemail

Это особенно полезно при работе с внешними API, где структура данных отличается от внутренней модели.


Работа с вложенными объектами

Вложенные структуры требуют явного указания включения на каждом уровне:

import { Expose, Type } from "class-transformer";

class Profile {
  @Expose()
  age: number;
}

export class UserDto {
  @Expose()
  id: number;

  @Expose()
  @Type(() => Profile)
  profile: Profile;
}

Если @Expose отсутствует в Profile, поля внутри profile могут быть потеряны при строгой трансформации.


Поведение с массивами

Массивы объектов требуют сочетания:

  • @Expose для полей;
  • @Type для указания типа элементов.
export class UserDto {
  @Expose()
  @Type(() => Profile)
  profiles: Profile[];
}

Без @Type трансформация массива может привести к потере структуры элементов.


Условия включения через группы

@Expose поддерживает группировку полей:

export class UserDto {
  @Expose({ groups: ["admin"] })
  role: string;

  @Expose({ groups: ["public"] })
  email: string;
}

При трансформации можно управлять набором полей:

plainToInstance(UserDto, data, {
  groups: ["public"]
});

Поведение:

  • поля вне группы игнорируются;
  • активируются только указанные группы.

Комбинация с @Exclude

@Exclude работает противоположно @Expose.

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

export class UserDto {
  @Expose()
  id: number;

  @Expose()
  email: string;

  @Exclude()
  password: string;
}

При excludeExtraneousValues: true:

  • password не попадёт в объект;
  • остальные поля включаются явно.

При этом @Exclude имеет более высокий приоритет в конфликтных ситуациях.


Типичные ошибки при использовании @Expose

Отсутствие глобального режима

Без excludeExtraneousValues декоратор не ограничивает входящие данные.

Частичное покрытие полей

Если часть полей не отмечена @Expose, результат может быть неполным объектом, что ломает логику валидации.

Потеря вложенных данных

Отсутствие @Type вместе с @Expose приводит к тому, что вложенные структуры остаются plain-объектами.

Несоответствие групп

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


Влияние на безопасность данных

Использование @Expose часто рассматривается как механизм защиты:

  • исключение паролей и токенов;
  • предотвращение утечки внутренних полей;
  • контроль структуры API-ответов;
  • снижение риска передачи лишних данных в бизнес-логику.

Однако сама по себе аннотация не является полноценной системой безопасности — она работает только в рамках корректно настроенной трансформации.


Роль в архитектуре DTO

В архитектуре DTO (Data Transfer Object) @Expose выполняет функцию контрактного слоя:

  • определяет, какие данные входят в систему;
  • фиксирует публичную структуру объекта;
  • отделяет внутренние модели от внешних представлений;
  • делает поведение данных предсказуемым при валидации.

При этом валидаторы из class-validator работают только с уже сформированным объектом, что делает этап трансформации критически важным.


Поведение при сериализации обратно в JSON

При обратном преобразовании (instanceToPlain) @Expose также влияет на итоговый JSON:

  • включаются только отмеченные поля;
  • вложенные структуры обрабатываются рекурсивно;
  • группы могут изменять набор экспортируемых данных.

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