You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现深度对象路径函数参数的智能提示与类型检查,规避递归类型限制

解决TypeScript嵌套对象路径访问函数的类型递归深度问题

问题背景

假设有如下嵌套对象:

const obj = {
  something: 123,
  otherThing: "asd",
  nested: {
    nestedSomething: 456,
    nestedOther: "fgh",
    deepNested: {
      deepNested1: "hello",
      deepNested2: 42,
      deepNestedArr: ["a", "b", "c"],
    },
  },
};

需要实现一个access函数,支持按路径访问对象值,示例如下:

access(obj, "something") // 返回number类型
access(obj, "nested", "nestedOther") // 返回string类型
access(obj, "nested", "deepNested", "deepNestedArr") // 返回string[]类型
access(obj, "nested", "deepNested", "deepNestedArr", 0) // 返回string类型

原实现的问题

最初实现了两个工具类型:

  1. AllPaths:生成对象所有叶子节点的路径联合类型
type AllPaths<Obj extends object, Key = keyof Obj> = Key extends keyof Obj
  ? Readonly<Obj[Key]> extends Readonly<Array<any>>
    ? [Key] | [Key, number]
    : Obj[Key] extends object
      ? [Key] | [Key, ...AllPaths<Obj[Key]>]
      : [Key]
  : never;
  1. GetTypeFromPath:根据对象类型和路径返回对应值的类型
type GetTypeFromPath<Obj extends object, Path extends PropertyKey[]> = Path extends [
  infer Head,
  ...infer Tail extends PropertyKey[]
]
  ? Head extends keyof Obj
    ? Obj[Head] extends object
      ? GetTypeFromPath<Obj[Head], Tail>
      : Obj[Head]
    : never
  : Obj;

但将它们用于泛型函数时,TypeScript服务会出现卡顿,并抛出「类型实例化过深」或「比较类型时栈深度过大」的错误:

declare function access<
  Obj extends object,
  Paths extends AllPaths<Obj> = AllPaths<Obj>,
  Ret = GetTypeFromPath<Obj, Paths>
>(
  obj: Obj,
  ...path: Paths
): Ret;

const res = access(obj, "nested", "deepNested"); // 类型计算失败

可行实现方案

方案1:函数重载(推荐,无递归深度问题)

通过编写多层函数重载,覆盖常用的嵌套深度(比如3-5层),既满足API边界的需求,又避免递归类型的性能问题:

// 基础情况:无路径参数,返回原对象
declare function access<T>(obj: T): T;

// 1层路径
declare function access<T, K1 extends keyof T>(obj: T, key1: K1): T[K1];

// 2层路径
declare function access<T, K1 extends keyof T, K2 extends keyof T[K1]>(
  obj: T,
  key1: K1,
  key2: K2
): T[K1][K2];

// 3层路径(支持数组索引)
declare function access<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2] | number>(
  obj: T,
  key1: K1,
  key2: K2,
  key3: K3
): K3 extends number 
  ? T[K1][K2] extends readonly (infer U)[] ? U : never
  : T[K1][K2][K3];

// 4层路径(支持数组索引)
declare function access<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2] | number, K4 extends keyof (
  K3 extends number ? (T[K1][K2] extends readonly (infer U)[] ? U : never) : T[K1][K2][K3]
) | number>(
  obj: T,
  key1: K1,
  key2: K2,
  key3: K3,
  key4: K4
): K4 extends number
  ? (K3 extends number 
      ? T[K1][K2] extends readonly (infer U)[] ? U extends readonly (infer V)[] ? V : never : never
      : T[K1][K2][K3] extends readonly (infer U)[] ? U : never
  )
  : K3 extends number
    ? (T[K1][K2] extends readonly (infer U)[] ? U[K4] : never)
    : T[K1][K2][K3][K4];

优势:

  • 无递归类型,TypeScript服务不会卡顿
  • 提供精确的智能提示和错误检查
  • 支持数组索引访问

方案2:限制递归深度的泛型类型

如果需要支持更深的嵌套,可以通过限制递归深度(比如设置最大深度为5)来避免触发TypeScript的递归限制:

// 设置最大递归深度
type MaxDepth = 5;

// 生成路径类型,限制深度
type PathImpl<T, Depth extends number[] = []> = 
  Depth['length'] extends MaxDepth
    ? never
    : T extends readonly any[]
      ? [number] | [number, ...PathImpl<T[number], [...Depth, 0]>]
      : T extends object
        ? keyof T extends infer K extends keyof T 
          ? [K] | [K, ...PathImpl<T[K], [...Depth, 0]>]
          : never
        : never;

type Path<T> = PathImpl<T>;

// 根据路径获取值类型,限制深度
type GetValueAtPath<T, P extends PropertyKey[], Depth extends number[] = []> =
  Depth['length'] extends MaxDepth
    ? T
    : P extends [infer First extends keyof T, ...infer Rest extends PropertyKey[]]
      ? GetValueAtPath<T[First], Rest, [...Depth, 0]>
      : T extends readonly (infer U)[]
        ? U
        : T;

declare function access<Obj extends object, P extends Path<Obj>>(
  obj: Obj,
  ...path: P
): GetValueAtPath<Obj, P>;

优势:

  • 支持更深的嵌套(可调整MaxDepth值)
  • 保持参数列表的灵活性
  • 同样提供智能提示和错误检查

验证效果

使用方案1或方案2后,以下调用都能正常推断类型并检查错误:

const res1 = access(obj, "something"); // number
const res2 = access(obj, "nested", "nestedOther"); // string
const res3 = access(obj, "nested", "deepNested", "deepNestedArr"); // string[]
const res4 = access(obj, "nested", "deepNested", "deepNestedArr", 0); // string
const res5 = access(obj, "invalidPath"); // 类型错误,提示无效路径

内容的提问来源于stack exchange,提问作者RestOfTheBothWorlds

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 12:51:09