如何实现支持指定嵌套路径的TypeScript递归可选化映射类型?
实现递归指定路径的部分可选映射类型
MyRecursivePartializer 需求回顾
给定原始类型:
type Bar = { foo: string; prop: number; baz: { value: string; check: boolean; } }
需要创建映射类型MyRecursivePartializer,支持指定嵌套路径将对应属性设为可选,且宽泛路径优先覆盖子路径:
- 指定
"foo"|"baz.check"时,仅foo和baz.check变为可选 - 指定
"foo"|"baz"时,foo和整个baz变为可选,且baz的所有子属性也递归可选
完整实现代码
// 拆分点分隔的路径为字符串数组 type SplitPath<T extends string> = T extends `${infer Head}.${infer Tail}` ? [Head, ...SplitPath<Tail>] : [T]; // 判断路径A是否被路径B覆盖(B是A的父路径) type IsPathCovered<T extends string, U extends string> = T extends `${U}.${infer _}` ? true : U extends `${T}.${infer _}` ? false : false; // 过滤掉被其他宽泛路径覆盖的子路径 type FilterCoveredPaths<T extends string> = T extends any ? Exclude<T, { [K in T]: IsPathCovered<K, Exclude<T, K>> extends true ? K : never }[T]> : never; // 核心递归部分可选类型 type MyRecursivePartializer<T, Paths extends string> = FilterCoveredPaths<Paths> extends infer FilteredPaths extends string ? { [K in keyof T]: K extends FilteredPaths ? // 当前属性匹配顶级路径,设为可选并递归Partial子属性 Partial<T[K]> | undefined : // 检查是否存在以当前属性开头的子路径,递归处理 (FilteredPaths extends `${K}.${infer SubPath}` ? MyRecursivePartializer<T[K], SubPath> : // 无匹配路径,保持原类型 T[K] ) } : never;
验证示例
示例1:指定"foo"|"baz.check"
type PartiallyBar = MyRecursivePartializer<Bar, "foo"|"baz.check">; // 生成的类型结构: // { // foo?: string | undefined; // prop: number; // baz: { // value: string; // check?: boolean | undefined; // }; // }
示例2:指定"foo"|"baz"
type PartiallyBar2 = MyRecursivePartializer<Bar, "foo"|"baz">; // 生成的类型结构: // { // foo?: string | undefined; // prop: number; // baz?: { // value?: string | undefined; // check?: boolean | undefined; // } | undefined; // }
逻辑说明
路径拆分与冲突处理:
SplitPath将嵌套路径拆分为数组,方便递归遍历嵌套属性。FilterCoveredPaths自动过滤被父路径覆盖的子路径,比如同时指定"baz"和"baz.check"时,会保留"baz"并丢弃"baz.check",确保宽泛路径优先生效。
递归属性处理:
- 遍历原类型的每个属性,若属性名直接匹配过滤后的路径,将其设为可选并递归将子属性全部设为可选(通过
Partial实现)。 - 若属性是嵌套对象且存在对应子路径,则递归进入嵌套对象处理指定子路径。
- 未匹配路径的属性保持原类型不变。
- 遍历原类型的每个属性,若属性名直接匹配过滤后的路径,将其设为可选并递归将子属性全部设为可选(通过
现成库方案
社区工具库type-fest提供了SetOptional(实现顶级属性可选)和DeepPartial(全递归可选),但没有直接支持指定嵌套路径+优先级覆盖的现成工具。你可以基于type-fest的基础类型扩展实现上述逻辑,或者直接使用上面的自定义类型。
内容的提问来源于stack exchange,提问作者robkuz
相关产品推荐
相关产品推荐

