使用nestjs-zod时如何在响应中序列化Date且不破坏Swagger
问题
我正在使用nestjs-zod包构建NestJS API,用于请求/响应校验和Swagger生成。我的领域实体如下:
export class UserEntity { constructor( public readonly id: string, public readonly username: string, public readonly hashPassword: string, public readonly is_active: boolean, public readonly created_at: Date, public readonly updated_at: Date | null, ) {} }
当前Zod响应schema:
import { z } from "zod" import { createZodDto } from "nestjs-zod" export const registerResponseSchema = z.object({ id: z.string(), username: z.string(), created_at: z.string(), }) export class RegisterResponse extends createZodDto(registerResponseSchema) {}
控制器代码:
import { Body, Controller, Post } from '@nestjs/common' import { ApiOperation, ApiTags } from '@nestjs/swagger' import { ZodResponse } from 'nestjs-zod' @ApiTags('Auth') @Controller('auth') export class AuthController { constructor(private readonly register: RegisterUseCase) {} @ApiOperation({ summary: 'Registrations' }) @ZodResponse({ status: 201, type: RegisterResponse, }) @Post('register') async registration(@Body() body: RegisterRequest) { return await this.register.register(body.username, body.password) } }
现在遇到的矛盾:
- 实体返回的
created_at是Date类型 - 若将响应schema定义为
created_at: z.string(),Swagger可正常生成,但运行时校验失败(返回值是Date而非字符串) - 若改为
created_at: z.date(),运行时校验正常,但Swagger生成时会报错:Date cannot be represented in JSON Schema
需要同时满足三个需求:
- 使用ZodResponse校验响应
- 生成Swagger/OpenAPI文档
- 实体中保留Date类型
请问使用nestjs-zod结合Swagger时,处理响应中Date字段的推荐方案是什么?
推荐解决方案
方案1:用Zod强制转换实现自动类型适配
借助Zod的coerce.string()结合transform,实现Date到字符串的自动转换,同时让Swagger识别为string类型:
import { z } from "zod" import { createZodDto } from "nestjs-zod" export const registerResponseSchema = z.object({ id: z.string(), username: z.string(), // 强制将Date转为ISO字符串,兼容运行时校验与Swagger文档 created_at: z.coerce.string().transform(val => new Date(val).toISOString()), }) export class RegisterResponse extends createZodDto(registerResponseSchema) {}
该方案无需修改实体或业务逻辑,Zod会自动完成类型转换与校验,Swagger也会生成符合JSON Schema规范的string类型定义。
方案2:自定义Zod类型适配Swagger规范
利用nestjs-zod的extendApi方法,给Zod的date类型附加Swagger兼容的格式定义:
import { z } from "zod" import { createZodDto } from "nestjs-zod" import { extendApi } from "nestjs-zod/dist/swagger" // 自定义类型:校验Date并转为ISO字符串,同时指定Swagger格式 const dateAsString = z.date().transform(date => date.toISOString()) extendApi(dateAsString, { type: 'string', format: 'date-time' }) export const registerResponseSchema = z.object({ id: z.string(), username: z.string(), created_at: dateAsString, }) export class RegisterResponse extends createZodDto(registerResponseSchema) {}
此方案既保留了Zod对Date类型的运行时校验,又通过扩展Swagger定义解决了JSON Schema不支持Date的问题,文档会显示为string(date-time)类型。
方案3:在业务层提前转换Date为字符串
在UseCase或控制器中,将实体的Date字段转为ISO字符串后再返回,保持Zod schema为z.string():
// 修改RegisterUseCase的register方法 async register(username: string, password: string): Promise<{id: string; username: string; created_at: string}> { // 业务逻辑生成UserEntity实例 const user = new UserEntity(/* 初始化参数 */) // 转换Date字段为字符串后返回 return { id: user.id, username: user.username, created_at: user.created_at.toISOString(), } }
此时Zod schema无需修改,运行时校验与Swagger文档生成均可正常工作,实体依然保留Date类型。
内容的提问来源于stack exchange,提问作者Никита Скобелев
相关产品推荐
相关产品推荐

