Zod+TypeScript:提取z.object特定键值到列表的类型错误排查
问题分析与解决
问题原因
当泛型参数T是多个Zod Schema的联合类型时,TypeScript对z.infer<T>["items"]的类型解析会出现偏差:它会将所有Schema对应的items类型合并为交叉类型,而非预期的联合类型。这是TypeScript的设计行为——对联合类型进行索引访问时,若所有成员都包含该属性,TypeScript会返回这些属性类型的交叉,以此保证类型安全(理论上要满足所有联合成员的类型约束),但这和我们实际需要的“每个Schema对应自己的items类型”的需求冲突,导致push元素时出现类型不匹配错误。
解决办法
方法1:用分发条件类型提取正确的联合类型
通过自定义类型工具,利用TypeScript的条件类型分发特性,从联合Schema中提取出对应的items元素类型的联合:
import { z } from "zod"; // 示例Schema const SchemaA = z.object({ items: z.array(z.string()) }); const SchemaB = z.object({ items: z.array(z.number()) }); type PossibleSchemas = typeof SchemaA | typeof SchemaB; // 提取单个Schema对应的items元素类型 type ExtractItemType<T extends z.ZodType<any, any, { items: any[] }>> = z.infer<T>["items"][number]; // 对联合Schema进行分发,得到items元素的联合类型 type ExtractItemsUnion<T> = T extends z.ZodType<any, any, { items: any[] }> ? ExtractItemType<T> : never; function lookDeep<T extends PossibleSchemas>(schema: T, data: unknown) { const valid = schema.parse(data); // 明确extractedItems的类型为联合类型数组 const extractedItems: ExtractItemsUnion<T>[] = []; // 用类型断言告诉TypeScript当前item属于联合类型的一员 valid.items.forEach(item => extractedItems.push(item as ExtractItemsUnion<T>)); return extractedItems; }
方法2:拆分函数,处理单个Schema而非联合
将通用函数拆分为处理单个Schema的基础函数,再针对联合Schema做分支判断,避免TypeScript对联合类型的索引解析出错:
import { z } from "zod"; const SchemaA = z.object({ items: z.array(z.string()) }); const SchemaB = z.object({ items: z.array(z.number()) }); // 基础函数:仅处理单个带有items数组的Zod Schema function lookDeepSingle<T extends z.ZodType<any, any, { items: any[] }>>(schema: T, data: unknown) { const valid = schema.parse(data); const extractedItems: z.infer<T>["items"][number][] = []; valid.items.forEach(item => extractedItems.push(item)); return extractedItems; } // 对外暴露的函数:处理联合Schema的情况 function lookDeep(schema: typeof SchemaA | typeof SchemaB, data: unknown) { if (schema === SchemaA) { return lookDeepSingle(schema, data); } return lookDeepSingle(schema, data); }
方法3:类型断言(不推荐,仅在确定类型安全时使用)
如果能确保当前处理的item类型和目标数组类型匹配,可以直接用类型断言跳过检查:
valid.items.forEach(item => extractedItems.push(item as any));
注意:这种方式会丢失TypeScript的类型检查,可能引入潜在的类型错误,仅作为临时解决方案。
内容的提问来源于stack exchange,提问作者Nate
相关产品推荐
相关产品推荐

