如何实现泛型化TypeScript嵌套类型的指定值类型点路径验证
泛型化嵌套对象的点格式路径类型验证
需求说明
现有对象类型:
type exampleType = { propertyOne: string propertyTwo: number, propertyThree: { propertyFour: string, propertyFive: Date, propertySix: boolean, } }
需要实现一个类型,用来验证点格式字符串,确保它仅指向值类型为string或Date的路径,最终期望的编译结果为:
propertyOne | propertyThree.propertyFour | propertyThree.propertyFive
已实现的基础版本
借助已有方案,已经实现了仅针对字符串类型路径的验证:
type PathsToStringProps<T> = T extends string ? [] : { [K in Extract<keyof T, string>]: [K, ...PathsToStringProps<T[K]>] }[Extract<keyof T, string>]; type Join<T extends string[], D extends string> = T extends [] ? never : T extends [infer F] ? F : T extends [infer F, ...infer R] ? F extends string ? `${F}${D}${Join<Extract<R, string[]>, D>}` : never : string; type Path = Join<PathsToStringProps<exampleType>, ".">
泛型化遇到的问题
希望将方案泛型化,让Path接受两个泛型参数:T(对应示例中的exampleType)和V(对应示例中的string|Date)。
- 泛型化
T时,最初的定义type Path<T> = Join<PathsToStringProps<T>, ".">出现Excessive stack depth comparing types 'PathsToStringProps<T>' and 'string[]'.ts(2321)错误,通过指定T extends {[key: string]: any}解决了该问题。 - 但添加
V参数限制路径指向的值类型时,如下代码出现类型错误:
type PathsToStringProps<T, V> = T extends (V) ? [] : { [K in Extract<keyof T, string>]: [K, ...PathsToStringProps<T[K], V>] }[Extract<keyof T, string>]; type Join<T extends string[], D extends string> = T extends [] ? never : T extends [infer F] ? F : T extends [infer F, ...infer R] ? F extends string ? `${F}${D}${Join<Extract<R, string[]>, D>}` : never : string; type Path<T extends {[key: string]: any}, V> = Join<PathsToStringProps<T, V>, ".">
- 若移除
Path的泛型参数V,仅在PathsToStringProps中写死V为string|Date,错误则消失:
type Path<T extends {[key: string]: any}> = Join<PathsToStringProps<T, string|Date>, ".">
解决方案
问题出在递归类型的约束和泛型参数传递上,需要给递归类型添加更明确的分支判断,避免TypeScript无法正确推断递归终止条件。修正后的完整代码如下:
type PathsToValueType<T, V> = // 终止条件:当前类型匹配目标值类型,返回空数组标记路径结束 T extends V ? [] : // 仅对对象类型递归遍历键,非对象且不匹配V的类型直接排除 T extends object ? { [K in Extract<keyof T, string>]: [K, ...PathsToValueType<T[K], V>] }[Extract<keyof T, string>] : never; type Join<T extends string[], D extends string> = T extends [] ? never : T extends [infer F] ? F extends string ? F : never : T extends [infer F, ...infer R] ? F extends string ? `${F}${D}${Join<Extract<R, string[]>, D>}` : never : never; type Path<T extends Record<string, any>, V> = Join<PathsToValueType<T, V>, ".">; // 测试示例 type exampleType = { propertyOne: string propertyTwo: number, propertyThree: { propertyFour: string, propertyFive: Date, propertySix: boolean, } }; type ValidPaths = Path<exampleType, string | Date>; // 结果:"propertyOne" | "propertyThree.propertyFour" | "propertyThree.propertyFive"
修正说明
- 给
PathsToValueType增加了T extends object的分支判断,明确仅对对象类型进行递归遍历,避免非对象类型触发不必要的递归。 - 补充了非对象且不匹配目标类型的分支,返回
never直接排除这类无效路径,让类型推断逻辑更清晰。 - 优化
Join类型的推断逻辑,增加对路径片段的显式字符串类型判断,避免潜在的类型兼容性问题。
内容的提问来源于stack exchange,提问作者Ali Bdeir
相关产品推荐
相关产品推荐

