序列化(Serialization)指的是在将对象作为响应发送给客户端之前,对其内容进行转换与清理的过程。在这一阶段,我们可以对响应数据进行加工处理,例如隐藏敏感信息(如密码)或筛选部分字段进行返回。
如果手动完成这些操作,不仅重复劳动、易出错,还难以维护。为此,Nest 提供了一套声明式、可扩展的解决方案,帮助你高效、安全地管理返回数据的结构和内容。
Nest 内置的 ClassSerializerInterceptor 拦截器,基于强大的 class-transformer 库,实现了对象序列化的自动化处理。它的工作流程如下:
instanceToPlain() 方法,将类实例转换为普通对象。@Exclude()、@Expose() 等)定义的转换规则。这种方式既简洁又灵活,可以极大地提升开发效率和代码可读性。
对于通过 StreamableFile
返回的文件流响应,序列化规则不会生效。
在实际开发中,我们通常希望避免将敏感信息(如用户密码)暴露给客户端。class-transformer 提供的 @Exclude() 装饰器可以帮助我们轻松实现这一目标。假设我们希望在返回用户数据时自动排除 password 属性,可以按如下方式编写实体类:
import { Exclude } from 'class-transformer'
export class UserEntity {
id: number
firstName: string
lastName: string
@Exclude()
password: string
constructor(partial: Partial<UserEntity>) {
Object.assign(this, partial)
}
}接下来,我们在控制器中返回该类的实例:
import { ClassSerializerInterceptor } from '@nestjs/common'
@UseInterceptors(ClassSerializerInterceptor)
@Get()
findOne(): UserEntity {
return new UserEntity({
id: 1,
firstName: 'John',
lastName: 'Doe',
password: 'password',
})
}必须返回实体类的实例,而不能是普通的 JavaScript 对象(如 {{ '{ user: new UserEntity() }' }})。否则,序列化过程将无法正确识别并应用 @Exclude() 等装饰器。
当客户端访问该接口时,收到的响应将自动排除 password 字段,结果如下所示:
{
"id": 1,
"firstName": "John",
"lastName": "Doe"
}值得一提的是,ClassSerializerInterceptor 拦截器不仅可以在控制器中局部使用,也可以应用到整个应用中(详见绑定拦截器一节)。借助拦截器与实体类的组合,我们可以在全局范围内统一控制响应结构,从而确保所有返回 UserEntity 的接口都自动排除了敏感字段。这是一种优雅且集中式的业务规则实现方式。
你可以使用 @Expose() 装饰器为类属性指定别名,或者将某个计算逻辑以 getter 的形式暴露出来。例如:
@Expose()
get fullName(): string {
return `${this.firstName} ${this.lastName}`
}这段代码会在序列化时,将 fullName 属性动态地拼接为 firstName 与 lastName 的组合。
如果你需要在序列化过程中对属性值进行进一步处理,可以使用 @Transform() 装饰器。例如,以下示例中只会返回 role 对象中的 name 字段,而不会暴露整个对象结构:
@Transform(({ value }) => value.name)
role: RoleEntity在某些场景下,你可能希望全局或局部调整序列化行为。此时,可以通过 @SerializeOptions() 装饰器传入一个配置对象,用于覆盖默认设置:
import { SerializeOptions } from '@nestjs/common'
@SerializeOptions({
excludePrefixes: ['_'],
})
@Get()
findOne(): UserEntity {
return new UserEntity()
}上述代码表示:凡是属性名称以 _ 开头的字段,在序列化结果中都会被自动排除。这些选项会作为底层 instanceToPlain() 方法的第二个参数进行传递。
你可以在控制器层统一配置序列化逻辑,通过 @SerializeOptions 装饰器指定返回类型,确保所有响应 —— 无论是普通对象还是类实例 —— 都会被自动转换为指定的类,并应用类上的装饰器(如 @Expose()、@Transform() 等)。这种做法可以大大简化代码逻辑,省去频繁手动调用 plainToInstance() 或显式实例化类的步骤。
下面的示例展示了如何自动将返回的普通 JavaScript 对象转换为 UserEntity 实例:
@UseInterceptors(ClassSerializerInterceptor)
@SerializeOptions({ type: UserEntity })
@Get()
findOne(@Query() { id }: { id: number }): UserEntity {
if (id === 1) {
return {
id: 1,
firstName: 'John',
lastName: 'Doe',
password: 'password',
}
}
return {
id: 2,
firstName: 'Kamil',
lastName: 'Mysliwiec',
password: 'password2',
}
}无论是哪个分支的返回值,最终都会被自动转换为 UserEntity 实例,并应用相应的序列化装饰器(如隐藏 password 字段等)。
为控制器指定明确的返回类型(如上例中的 UserEntity)还能让 TypeScript
发挥其类型检查优势,确保返回数据结构符合 DTO 或实体定义。相比之下,直接调用
plainToInstance()
不会提供这类类型校验机制,可能会掩盖结构不一致的问题,增加调试成本。
想要深入了解序列化的实际应用,可以参考 Nest 官方仓库中的 21-serializer 示例。该示例涵盖了装饰器的使用方式、自动转换逻辑等,能够帮助你更直观地掌握相关概念。
本章内容主要围绕 HTTP 应用(如 Express 或 Fastify)展开,但值得注意的是,ClassSerializerInterceptor 同样适用于 WebSocket 和微服务架构。无论你采用哪种传输协议,都可以正常使用该拦截器,实现统一的数据序列化逻辑。
如果你希望了解更多可用的装饰器、转换策略及高级配置项,建议查阅 class-transformer 的官方文档,它是 Nest 序列化机制的底层依赖库。