如何递归为Zod Schema应用.passthrough()或修改默认行为?
问题描述
我正在对接一个返回复杂嵌套数据结构且频繁变更(新增数据等)的API。我希望验证API返回的最小属性集,同时保留所有其他属性(代码需读取、更新并回传这些数据),因此在Zod Schema中使用了.passthrough()方法。
该方法运行正常,但需要逐个为每个内部Schema调用.passthrough()才能避免丢失额外属性,示例代码如下:
/* sample data*/ const someObjectFromAnApi = JSON.parse(`{ "coords": { "x": 42, "y": 0, "z": 30 }, "color": { "r": 20, "g": 30, "b": 30, "alpha": 0.5 }, "description": "This is here" }`); /* /sample data*/ const dataSchema = z .object({ coords: z .object({ x: z.number(), y: z.number() // 不需要验证`z`属性 }) .passthrough(), // 第一个passthrough color: z .object({ r: z.number(), g: z.number(), b: z.number() // 不需要验证`alpha`属性 }) .passthrough() // 第二个passthrough }) // 不需要验证`description`属性 .passthrough(); //第三个passthrough
我的问题是:是否有简便方法递归应用.passthrough(),或修改Zod的默认行为?
我尝试编写如下工具文件,但语法存在问题:
import { z, ZodType } from 'zod'; type ZObject = typeof z.object; declare module 'zod' { export interface ZodType { objectPassthrougth : ZObject; } } z.ZodObject.prototype.objectPassthrougth = function(shape, params){ return z.object(shape,params).passthrough(); } const schema = z.objectPassthrougth({ some : z.string() })
解决方案
方案1:自定义递归工具函数
编写一个递归函数,遍历整个Schema结构,自动为所有嵌套的ZodObject添加.passthrough():
import { z, ZodObject, ZodTypeAny } from 'zod'; function recursivePassthrough<T extends ZodTypeAny>(schema: T): T { if (schema instanceof ZodObject) { // 递归处理每个子schema const newShape = Object.fromEntries( Object.entries(schema.shape).map(([key, subSchema]) => [ key, recursivePassthrough(subSchema) ]) ); return z.object(newShape).passthrough() as unknown as T; } // 非对象类型直接返回原schema return schema; } // 使用示例 const dataSchema = recursivePassthrough(z.object({ coords: z.object({ x: z.number(), y: z.number() }), color: z.object({ r: z.number(), g: z.number(), b: z.number() }) })); // 验证后会保留所有未定义的属性 const result = dataSchema.parse(someObjectFromAnApi);
方案2:扩展Zod,创建默认带Passthrough的Object方法
如果你想修改Zod的默认行为,让自定义的对象方法自动递归应用.passthrough(),可以修正你之前的扩展代码:
import { z, ZodObject, ZodRawShape } from 'zod'; // 扩展Zod的类型声明 declare module 'zod' { interface ZodType { objectPassthrough: <Shape extends ZodRawShape>( shape: Shape, params?: Parameters<typeof z.object>[1] ) => ZodObject<Shape>; } } // 实现自定义方法 z.objectPassthrough = function<Shape extends ZodRawShape>( shape: Shape, params?: Parameters<typeof z.object>[1] ) { // 递归处理嵌套对象 const processedShape = Object.fromEntries( Object.entries(shape).map(([key, subSchema]) => { if (subSchema instanceof ZodObject) { return [key, z.objectPassthrough(subSchema.shape)]; } return [key, subSchema]; }) ) as Shape; return z.object(processedShape, params).passthrough(); }; // 使用示例 const dataSchema = z.objectPassthrough({ coords: z.object({ x: z.number(), y: z.number() }), color: z.object({ r: z.number(), g: z.number(), b: z.number() }) });
注意事项
- 上述代码基于Zod v3版本编写,若使用v4版本需根据官方API调整细节;
- 若API返回数据存在循环引用,需在递归函数中添加缓存逻辑,避免栈溢出。
内容的提问来源于stack exchange,提问作者Steve B
相关产品推荐
相关产品推荐

