NestJS 中如何序列化 Date 响应而不影响 Swagger?
数据阿明
结论
领域实体中的 Date 可以保留。构造 HTTP 响应时,将它转换为 ISO 8601 字符串,再用字符串 schema 校验响应。领域模型使用应用内部的数据类型,HTTP 响应则要符合 JSON 中实际传输的类型;JSON 没有原生的 Date 类型。
为什么 z.date() 会导致问题
题目中的实体将 created_at 定义为 Date,而 z.string() 只接受字符串。直接把实体交给 ZodResponse,运行时校验会因类型不符而失败。
改用 z.date() 可以校验运行时的 Date,但 JSON Schema 和 OpenAPI 描述的是可传输的数据结构,无法直接表示 JavaScript 的 Date 实例。因此,生成文档时会报错:Date cannot be represented in JSON Schema。
将实体映射为响应对象
让响应 schema 描述客户端实际收到的 JSON 字符串,并在返回响应前显式转换日期:
import { z } from 'zod'
import { createZodDto } from 'nestjs-zod'
export const registerResponseSchema = z.object({
id: z.string(),
username: z.string(),
created_at: z.string().datetime(),
})
export class RegisterResponse extends createZodDto(registerResponseSchema) {}
在 controller 中,将领域对象映射为响应所需的结构:
@ZodResponse({
status: 201,
type: RegisterResponse,
})
@Post('register')
async registration(@Body() body: RegisterRequest) {
const user = await this.register.register(body.username, body.password)
return {
id: user.id,
username: user.username,
created_at: user.created_at.toISOString(),
}
}
这样,ZodResponse 会校验字符串,Swagger/OpenAPI 也会将字段描述为日期时间字符串,实体则可以继续使用 Date。如果项目使用的 Zod 版本不支持 .datetime(),可以根据版本改用对应的日期字符串校验方式,或使用 z.string(),并确保映射时输出 ISO 字符串。
注意事项
toISOString()会生成 UTC 格式的 ISO 8601 字符串。请确认这符合 API 对时区和精度的约定。- 如果字段允许为空,响应 schema 应与映射结果一致。例如,schema 允许
null时,映射也应保留null。 - 不要只依赖
JSON.stringify()自动转换日期。显式映射能让响应校验和 API 文档都清楚地反映实际返回结构。
备注:内容仅供参考。
