如何在TypeScript中按ID追踪嵌套对象的完整路径类型?
问题:根据ID查找嵌套对象并生成完整路径类型
背景与需求
定义如下嵌套对象结构:
type IObject = { id: string, path: string, children?: IObject[] } const tree = [ { id: 'obj1' as const, path: 'path1' as const, children: [ { id: 'obj2' as const, path: ':pathParam' as const, children: [ { id: 'obj3' as const, path: 'path3' as const } ] }, { id: 'obj4' as const, path: 'path4' as const, children: [ { id: 'obj5' as const, path: 'path5' as const } ] } ] } ];
需要实现类型FindAndTrackDeepPath<ID, T>,根据指定ID从树结构T中找到对应对象类型,同时附加fullPath字段存储根到该对象的完整路径(路径片段用/拼接)。例如:
type ok = FindAndTrackDeepPath<'obj2', typeof tree>['fullPath']; // 期望类型:"/path1/:pathParam"
原实现的问题
尝试的实现如下,但查找深层节点时会生成错误的联合路径:
export type FindAndTrackDeepPath< ID, T extends IObject[], depth extends number = 1, path extends string = '', maxDepth extends number = 10 > = depth extends maxDepth ? never : FindByID<ID, T> extends never ? FindAndTrackDeepPath< ID, Descend<T>['children'], Increment<depth>, Descend<T>['path'] extends undefined ? '' : `${path}/${Descend<T>['path']}` > : FindByID<ID, T> & { fullPath: `${path}/${FindByID<ID, T>['path']}` }; // 辅助类型 type FindByID<ID, T extends IObject[]> = Extract<T[number], { id: ID }>; type Descend<T extends IObject[]> = Extract<T[number], { children: IObject[] }>; type Increment<N extends number> = [1,2,3,4,5,6,7,8,9,10,11,12,...number[]][N]; // 错误示例 type notOK = FindAndTrackDeepPath<'obj3', typeof tree>['fullPath']; // 实际得到:"/path1/:pathParam/path3" | "/path1/path4/path3",其中第二个路径是错误的
问题根源:Descend<T>会提取当前层级所有带children的节点形成联合类型,递归时会同时遍历所有分支的children,导致路径拼接时混入不相关分支的path,生成错误的联合结果。
正确实现方案
改为深度优先、单分支递归:针对每个节点单独处理,要么找到目标ID返回路径,要么递归遍历该节点的children并传递当前已拼接的路径。
实现代码:
type IObject = { id: string, path: string, children?: IObject[] }; // 处理单个节点的逻辑 type FindInNode<ID, Node extends IObject, CurrentPath extends string> = // 当前节点是目标,返回带完整路径的类型 Node['id'] extends ID ? Node & { fullPath: `${CurrentPath}/${Node['path']}` } : // 当前节点有children,递归遍历子节点 Node['children'] extends infer C extends IObject[] ? FindAndTrackDeepPath<ID, C, `${CurrentPath}/${Node['path']}`> : never; // 入口类型:遍历数组中的每个节点 type FindAndTrackDeepPath<ID, T extends IObject[], CurrentPath extends string = ''> = T[number] extends infer Node extends IObject ? FindInNode<ID, Node, CurrentPath> : never; // 测试用例 const tree = [ { id: 'obj1' as const, path: 'path1' as const, children: [ { id: 'obj2' as const, path: ':pathParam' as const, children: [ { id: 'obj3' as const, path: 'path3' as const } ] }, { id: 'obj4' as const, path: 'path4' as const, children: [ { id: 'obj5' as const, path: 'path5' as const } ] } ] } ] as const; type ok1 = FindAndTrackDeepPath<'obj2', typeof tree>['fullPath']; // type ok1 = "/path1/:pathParam" type ok2 = FindAndTrackDeepPath<'obj3', typeof tree>['fullPath']; // type ok2 = "/path1/:pathParam/path3" type ok3 = FindAndTrackDeepPath<'obj5', typeof tree>['fullPath']; // type ok3 = "/path1/path4/path5"
实现逻辑说明
FindInNode:负责单个节点的匹配与递归:- 若当前节点ID匹配目标,返回该节点类型并附加拼接完成的
fullPath。 - 若当前节点有子节点,递归调用
FindAndTrackDeepPath处理子节点数组,同时传递当前已拼接的路径。
- 若当前节点ID匹配目标,返回该节点类型并附加拼接完成的
FindAndTrackDeepPath:作为入口遍历输入数组的每个节点,调用FindInNode处理。由于ID唯一,最终只会返回匹配的单个分支结果,不会生成错误的联合类型。
内容的提问来源于stack exchange,提问作者Jan Kretschmer
相关产品推荐
相关产品推荐

