PAYATHON 2026

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 文档都清楚地反映实际返回结构。

备注:内容仅供参考。