如何使用Zod按类型从对象Schema中提取生成新Schema?
问题
假设有如下示例Zod Schema:
const Z_GlobalSettings = z.object({ crust: z.enum(["thin", "thick"]).default("thin"), toppings: z.array(z.string()).min(1).max(5).default(["cheese"]), sauce: z.enum(["marinara", "bbq", "ranch"]).default("marinara"), delivery_time: z.string().optional(), order_notes: z.string().max(50).optional(), contact_email: z.string().email().optional(), setting_1: z.boolean(), setting_2: z.boolean().optional(), }); type GlobalSettings = z.infer<typeof Z_GlobalSettings>;
如何通过编程方式提取布尔类型或任意指定类型的字段,生成新的Zod Schema?比如提取布尔类型字段后,期望得到的新Schema为:
const Z_OnlyBoolean = z.object({ setting_1: z.boolean(), setting_2: z.boolean().optional(), });
最初我通过TypeScript类型推导实现了需求:
type BooleanSettings = { [K in keyof GlobalSettings as GlobalSettings[K] extends boolean | undefined ? K : never]?: boolean; };
但这种方式存在缺陷:无法区分z.string()和z.enum(["example1", "example2"]),因为二者推导后的TypeScript类型均为string。而Zod本身是可以区分这两种类型的。
目前只能通过Zod文档中的.pick()方法手动选择字段,但后续新增字段时需要同步维护过滤后的Schema,且每种类型都要手动创建新Schema,可用性很差。请问该如何实现按Zod类型自动提取并生成新Schema?
解决方案
要实现按Zod自身的类型自动提取字段,核心是利用Zod Schema实例的内部属性判断字段类型,而非依赖TypeScript推导后的类型。下面提供通用的实现方案:
1. 编写通用类型提取工具函数
这个函数接收原始Schema和类型判断逻辑,返回过滤后的新Schema:
import { z, ZodType, ZodObject } from "zod"; function pickByZodType<T extends ZodObject<any>>( originalSchema: T, isTargetType: (schema: ZodType) => boolean ) { const filteredFields: Record<string, ZodType> = {}; // 遍历原始Schema的所有字段 for (const [key, schema] of Object.entries(originalSchema.shape)) { // 处理可选字段:取出ZodOptional包裹的原始类型 const actualSchema = schema instanceof z.ZodOptional ? schema.unwrap() : schema; // 判断字段是否符合目标类型 if (isTargetType(actualSchema)) { filteredFields[key] = schema; // 保留原字段的所有约束(optional、default等) } } return z.object(filteredFields); }
2. 提取布尔类型字段的示例调用
用上述工具函数提取布尔类型字段:
// 定义布尔类型的判断逻辑 const isBooleanSchema = (schema: ZodType) => schema instanceof z.ZodBoolean; // 生成仅包含布尔类型字段的Schema const Z_OnlyBoolean = pickByZodType(Z_GlobalSettings, isBooleanSchema);
生成的Z_OnlyBoolean会完整保留原字段的optional约束,完全符合预期。
3. 区分普通字符串与枚举类型的示例
如果要提取普通z.string()字段、排除z.enum()类型,可以这样写:
const isPlainStringSchema = (schema: ZodType) => { return schema instanceof z.ZodString && !(schema instanceof z.ZodEnum); }; // 提取仅包含普通字符串类型的字段 const Z_OnlyPlainString = pickByZodType(Z_GlobalSettings, isPlainStringSchema);
这样就能精准区分Zod的普通字符串和枚举类型,不会出现混淆。
核心优势
- 直接基于Zod实例的类型判断,完全规避TypeScript类型推导的局限性
- 保留原字段的所有约束(包括
optional、default、min/max等),无需重复定义 - 新增字段时,只要类型符合判断条件就会自动纳入新Schema,无需手动维护
内容的提问来源于stack exchange,提问作者Josh Andromidas
相关产品推荐
相关产品推荐

