В экосистеме работы с объектами данных в Node.js часто используются совместно две библиотеки: class-transformer и class-validator. Первая отвечает за преобразование plain-объектов в экземпляры классов и обратно, вторая — за проверку корректности данных через декораторы.
Ключевая проблема, возникающая при совместном использовании этих
инструментов, заключается в том, что не каждое поле входящего объекта
должно попадать в итоговый экземпляр класса. Управление этим процессом
осуществляется через механизм включения и исключения свойств,
центральным элементом которого выступает декоратор
@Expose.
Перед тем как данные попадут под проверку валидатора, они часто проходят этап преобразования:
Этот процесс выполняется с помощью plainToInstance.
Однако без явного управления включением полей все свойства могут быть
перенесены «как есть», что создаёт риск попадания нежелательных данных в
бизнес-логику.
@ExposeДекоратор @Expose используется для явного указания того,
какие поля должны быть включены в процесс сериализации и
десериализации.
Если включён режим исключения лишних свойств, то только поля с
@Expose будут попадать в объект:
Для того чтобы @Expose начал работать в строгом режиме,
необходимо включить параметр:
excludeExtraneousValues: trueОн передаётся в функции преобразования:
plainToInstance(UserDto, data, {
excludeExtraneousValues: true
});
Без этого параметра @Expose не ограничивает входящие
данные, а используется только как дополнительная мета-информация.
@Exposeimport { 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 включён, результат будет пустым
объектом.
Это часто приводит к ошибкам:
@Expose позволяет не только включать поля, но и изменять
их имя.
export class UserDto {
@Expose({ name: "user_id" })
id: number;
@Expose({ name: "user_email" })
email: string;
}
При преобразовании:
user_id → iduser_email → emailЭто особенно полезно при работе с внешними 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 часто рассматривается как механизм
защиты:
Однако сама по себе аннотация не является полноценной системой безопасности — она работает только в рамках корректно настроенной трансформации.
В архитектуре DTO (Data Transfer Object) @Expose
выполняет функцию контрактного слоя:
При этом валидаторы из class-validator работают только с уже сформированным объектом, что делает этап трансформации критически важным.
При обратном преобразовании (instanceToPlain)
@Expose также влияет на итоговый JSON:
Это позволяет использовать один и тот же класс как для входящих, так и для исходящих данных, при строгом контроле состава полей.