如何在TypeScript中结合Zod正确处理可选DTO的类型推断?
问题解决:TypeScript + Zod DTO类型推断错误
问题根源
你的代码中,DtoObject 类型将 params 等属性定义为可选的 z.ZodType | undefined,当你把 getTodoByIdDto 标注为 DtoObject 类型时,TypeScript 会将具体的 Zod Schema 类型“拓宽”为通用的 z.ZodType,导致后续的条件类型无法识别到实际的 Schema 结构,最终推断出 params 类型为 undefined,从而引发赋值错误。
解决方案
方案1:使用DTO创建函数保留Schema类型
通过工具函数创建DTO,避免类型拓宽,让TypeScript自动推断具体的Schema类型:
import { z } from 'zod'; // 用于创建DTO的工具函数,保留具体Schema类型 export function createDto<T extends { body?: z.ZodType; params?: z.ZodType; queryParams?: z.ZodType }>(dto: T) { return dto; } // 定义DTO,无需手动标注类型,由函数自动推断 export const getTodoByIdDto = createDto({ params: z.object({ todoId: z.string().openapi({ example: '1234' }), }), }); // 从DTO中提取数据类型的工具类型 type ExtractDtoTypes<T> = { [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never; }; // 生成最终的API请求类型 export type GetTodoById = ExtractDtoTypes<typeof getTodoByIdDto>; // 现在可以正常赋值,无类型错误 export const sample: GetTodoById = { params: { todoId: '1234', }, };
方案2:使用泛型DtoObject类型
如果你倾向于显式的类型标注,可以定义泛型DtoObject来捕获具体的Schema类型:
import { z } from 'zod'; // 泛型DTO类型,保存每个属性的具体Schema类型 export type DtoObject< Body extends z.ZodType | undefined = undefined, Params extends z.ZodType | undefined = undefined, Query extends z.ZodType | undefined = undefined > = { body?: Body; params?: Params; queryParams?: Query; }; // 显式指定泛型类型来定义DTO export const getTodoByIdDto: DtoObject<undefined, z.ZodObject<{ todoId: z.ZodString }>> = { params: z.object({ todoId: z.string().openapi({ example: '1234' }), }), }; // 提取params的推断类型 export type GetTodoById = { params: typeof getTodoByIdDto.params extends z.ZodType ? z.infer<typeof getTodoByIdDto.params> : undefined; }; // 合法的示例赋值 export const sample: GetTodoById = { params: { todoId: '1234', }, };
处理可选Zod Schema的最佳实践
- 避免类型拓宽:不要将具体的Schema实例标注为
z.ZodType,而是让TypeScript推断其具体类型,或使用泛型/工具函数保留类型信息。 - 复用类型提取逻辑:创建通用的
ExtractDtoTypes工具类型,避免为每个DTO重复编写条件类型。 - 利用Zod内置能力:Zod的
z.infer工具需要具体的Schema类型才能正确工作,避免使用通用的z.ZodType作为输入。
内容的提问来源于stack exchange,提问作者TheSmartMonkey
相关产品推荐
相关产品推荐

