Prisma日期对象无法通过Zod日期校验,求通用解决方案
问题
代码示例
export const getSales = procedure.query(async ({ ctx }) => { const sales = await ctx.prisma.sale.findMany({ where: { deletedAt: null }, }) return salesSelectOutput.parse(sales) })
saleSelectOutput 说明
saleSelectOutput是基于saleSchema修改/省略部分字段得到的Zod校验规则,saleSchema同时用于创建createSaleInputsalesSelectOutput是saleSelectOutput的数组版本
问题详情
代码运行无其他异常,但 sales.[n].dateOfSale 字段校验失败,错误信息:
"code": "invalid_type", "expected": "date", "received": "object", "path": [ "dateOfSale" ], "message": "please input a proper date"
查看 dateOfSale 的类型为 object,值为:
2024-02-28T08:09:50.339Z
仅针对该字段单独转换的方案不可行(如下),因为多处存在类似问题:
return salesSelectOutput.parse({...sales, dateOfSale: new Date(dateOfSale)})
需求
如何让Zod接受Prisma返回的日期对象,或在校验前统一转换这类值?
解决方案
方案1:修改Zod Schema兼容Prisma Date对象
Prisma返回的日期是Date实例,但Zod默认的z.date()可能因实例判断严格报错,可自定义兼容校验器:
import { z } from "zod"; // 自定义兼容Prisma Date的校验规则 const prismaDate = z.custom<Date>( (val) => val instanceof Date || (typeof val === 'object' && 'toISOString' in val), { message: "please input a proper date" } ); // 替换原schema中的z.date()为prismaDate const saleSchema = z.object({ // 其他字段... dateOfSale: prismaDate, // 其他字段... }); // 生成对应的输出校验规则 const saleSelectOutput = saleSchema.omit({ /* 需要省略的字段 */ }); const salesSelectOutput = z.array(saleSelectOutput);
方案2:校验前递归转换所有日期字段
编写工具函数统一处理查询结果中的日期字段,避免逐个修改:
// 递归转换日期字段的工具函数 const transformDates = (data) => { if (data instanceof Date) return new Date(data); if (Array.isArray(data)) return data.map(transformDates); if (typeof data === 'object' && data !== null) { return Object.fromEntries( Object.entries(data).map(([key, val]) => [key, transformDates(val)]) ); } return data; }; // 修改查询逻辑 export const getSales = procedure.query(async ({ ctx }) => { const sales = await ctx.prisma.sale.findMany({ where: { deletedAt: null }, }); const transformedSales = transformDates(sales); return salesSelectOutput.parse(transformedSales); });
方案3:让Prisma直接返回ISO字符串
在查询时指定日期字段返回ISO字符串,适配Zod的z.string().datetime()规则:
export const getSales = procedure.query(async ({ ctx }) => { const sales = await ctx.prisma.sale.findMany({ where: { deletedAt: null }, select: { // 保留其他需要的字段 dateOfSale: { select: { toISOString: true } }, }, }); // 提取ISO字符串 const processedSales = sales.map(sale => ({ ...sale, dateOfSale: sale.dateOfSale.toISOString, })); return salesSelectOutput.parse(processedSales); });
内容的提问来源于stack exchange,提问作者The Blind Hawk
相关产品推荐
相关产品推荐

