TypeScript中将嵌套类型指定属性设为可选的泛型实现
MakeOptional 嵌套属性可选泛型实现 可同时支持对象结构传参、点分隔字符串路径传参两种调用方式,仅将指定路径的属性(含任意深度嵌套属性)标记为可选,其余属性保持原类型、原必填状态不变。
完整实现代码
// 将点分隔的单条路径转为嵌套对象结构,例:"data.address" -> { data: { address: unknown } } type PathToObj<T extends string> = T extends `${infer Head}.${infer Rest}` ? { [K in Head]: PathToObj<Rest> } : { [K in T]: unknown }; // 联合类型转交叉类型,用于合并多条路径生成的对象结构 type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; // 将点分隔路径联合转为统一的嵌套对象结构 type PathsToObj<T extends string> = UnionToIntersection<T extends any ? PathToObj<T> : never>; // 核心递归逻辑:遍历原类型,命中可选路径的属性标记为可选,嵌套结构递归处理 type MakeOptionalDeep<T, Opts> = ( { [K in keyof T as K extends keyof Opts ? K : never]+?: K extends keyof Opts ? Opts[K] extends object ? MakeOptionalDeep<T[K], Opts[K]> : T[K] : never } & { [K in keyof T as K extends keyof Opts ? never : K]: T[K] } ) extends infer O ? { [K in keyof O]: O[K] } : never; // 对外暴露的工具类型,自动兼容两种入参格式 type MakeOptional<T, Opts extends string | Record<string, any>> = Opts extends string ? MakeOptionalDeep<T, PathsToObj<Opts>> : MakeOptionalDeep<T, Opts>;
使用示例
以给出的Person接口为例:
interface Person { name: string; hometown: string; nickname: string; data:{ address:string, phone:number } }
两种传参方式均可得到预期类型:
- 对象结构传参
type TransformedPerson = MakeOptional<Person,{name:string, data:{address:string}}> - 点分隔字符串路径传参
type TransformedPerson = MakeOptional<Person,"name"|"data.address">
最终生成的类型结构完全一致:
{ name?: string; hometown: string; nickname: string; data: { address?: string; phone: number; } }
实现说明
之前使用的RecursivePartial方案逻辑是递归遍历所有属性全部设为可选,无法精确指定部分属性保持必填,因此无法满足嵌套路径精准标记可选的需求。
上述实现的逻辑是先将两种入参格式统一转换为嵌套对象的路径描述结构,再递归遍历原类型:
- 若当前属性命中路径描述,则标记为可选;如果对应路径还有子层级,就递归处理子属性的可选性
- 若当前属性未命中路径描述,直接保留原类型、原必填状态,不做修改
- 支持任意深度的嵌套路径配置,不会修改原属性的原始类型,仅调整可选性标记
内容的提问来源于stack exchange,提问作者Waqar Mushtaq
相关产品推荐
相关产品推荐

