如何用TypeScript实现嵌套对象属性到带点路径字符串的类型映射?
问题
想要创建一个TypeScript类型,将对象的属性映射为字符串路径:
- 直接属性映射为属性名本身,比如
"a"、"x" - 若属性值是对象,则同时生成该属性名,以及该对象内部属性的路径(前面加上当前属性名和圆点),比如
"a.b"、"a.b.c"
示例对象:
{ a: { b: { c: 'hello' } }, x: { y: { z: 'hello' } } }
期望得到的类型:
"a" | "x" | "a.b" | "a.b.c" | "x.y" | "x.y.z"
尝试的实现代码:
interface A { a: number b: { c: string d: { e: { f: 'hello' } } } } type valueof<T> = T[keyof T] type str<T> = T extends string ? T : never type record<T> = valueof<{ [K in keyof T]: T[K] extends any[] ? never : T[K] extends Record<string, any> ? K | `${str<K>}.${str<record<T[K]>>}` : K }>
该代码逻辑上看似正确,但会触发无限递归导致TypeScript崩溃,需要其他可行实现方式。
解决方案
原实现的问题在于递归没有明确的终止边界,当处理嵌套对象时,TypeScript无法确定递归何时停止,进而引发无限递归。我们可以通过限制递归仅针对非数组、非基本类型的对象,利用TypeScript的递归深度限制(默认100层,足够处理大多数场景)来实现。
以下是修正后的实现:
// 排除数组和基本类型,仅保留纯对象类型 type IsPlainObject<T> = T extends any[] ? false : T extends object ? true : false; // 生成所有属性路径的核心类型 type ObjectPaths<T> = T extends object ? { [K in keyof T & string]: IsPlainObject<T[K]> extends true ? K | `${K}.${ObjectPaths<T[K]>}` : K }[keyof T & string] : never;
代码解释
IsPlainObject<T>:判断类型是否为纯对象(排除数组,因为数组虽属于object,但通常不需要展开其索引路径)。ObjectPaths<T>:- 先判断
T是否为对象类型,非对象则返回never - 遍历
T的所有字符串类型键K:- 若
T[K]是纯对象,生成两种路径:当前键K,以及K拼接子对象路径的组合(${K}.${ObjectPaths<T[K]>}) - 若
T[K]不是纯对象(如基本类型、数组),仅生成当前键K
- 若
- 最后通过
[keyof T & string]提取所有路径的联合类型
- 先判断
测试示例
用提供的interface A测试:
interface A { a: number b: { c: string d: { e: { f: 'hello' } } } } type APaths = ObjectPaths<A>; // 得到的类型:"a" | "b" | "b.c" | "b.d" | "b.d.e" | "b.d.e.f"
如果需要避免极端深层嵌套导致的性能问题,可手动添加递归深度限制:
type ObjectPathsWithDepth<T, Depth extends number = 10, CurrentDepth extends 0[] = []> = CurrentDepth['length'] extends Depth ? keyof T & string : T extends object ? { [K in keyof T & string]: IsPlainObject<T[K]> extends true ? K | `${K}.${ObjectPathsWithDepth<T[K], Depth, [...CurrentDepth, 0]>}` : K }[keyof T & string] : never; // 限制最多3层递归 type APathsLimited = ObjectPathsWithDepth<A, 3>; // 得到:"a" | "b" | "b.c" | "b.d" | "b.d.e"
内容的提问来源于stack exchange,提问作者asnaeb
相关产品推荐
相关产品推荐

