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

如何让TypeScript中读取对象路径的getIn函数实现类型安全?

实现类型安全的对象路径读取辅助函数

我想要创建一个读取对象路径的简易辅助函数,示例如下:

interface Human {
  address: {
    city: {
      name: string;
    }
  }
}

const human: Human = { address: { city: { name: "Town"}}};
getIn<Human>(human, "address.city.name"); // 返回"Town"

该辅助函数在JavaScript中很容易实现,但在TypeScript中实现类型安全则较为复杂。我目前的实现如下:

type Prev = [never, 0, 1, 2, 3, 4, 5, 6, ...0[]];

type Join<K, P> = K extends string | number
  ? P extends string | number
    ? `${K}${"" extends P ? "" : "."}${P}`
    : never
  : never;

type Path<T, D extends number = 4> = [D] extends [never]
  ? never
  : T extends object
  ? {
      [K in keyof T]-?: K extends string | number
        ? `${K}` | Join<K, Path<T[K], Prev[D]>>
        : never;
    }[keyof T]
  : "";

function getIn<T extends Record<string, any>>(object: T, path: Path<T>): T {
  const parts = path.split(".");
  return parts.reduce<T>((result, key) => {
    if (result !== undefined && result[key]) {
      return result[key];
    }

    return undefined;
  }, object);
}

这段代码可以运行,但存在问题:getIn函数的返回类型不应为T,而应根据传入的路径返回对象T内部的对应类型。例如调用getIn<Human>(human, "address.city.name")时,TypeScript应识别返回值为string;传入"address.city"时,返回类型应为对应的{ name: string }类型等。请问有什么方法可以实现该函数的类型安全?


解决方案:实现路径对应的类型提取

要让getIn返回正确的类型,我们需要新增一个PathValue类型,用来根据传入的对象类型T和路径P,递归提取最终的目标类型。同时需要调整getIn的泛型定义,让它关联路径和返回类型:

完整实现代码

// 用于递归深度递减的辅助类型
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, ...0[]];

// 拼接路径片段的辅助类型
type Join<K, P> = K extends string | number
  ? P extends string | number
    ? `${K}${"" extends P ? "" : "."}${P}`
    : never
  : never;

// 生成对象所有合法路径的类型
type Path<T, D extends number = 4> = [D] extends [never]
  ? never
  : T extends object
  ? {
      [K in keyof T]-?: K extends string | number
        ? `${K}` | Join<K, Path<T[K], Prev[D]>>
        : never;
    }[keyof T]
  : "";

// 根据路径提取对应值的类型
type PathValue<T, P extends Path<T>> = 
  P extends `${infer K}.${infer Rest}`
    ? K extends keyof T
      ? Rest extends Path<T[K]>
        ? PathValue<T[K], Rest>
        : never
      : never
    : P extends keyof T
      ? T[P]
      : never;

// 类型安全的getIn函数
function getIn<T extends Record<string, any>, P extends Path<T>>(object: T, path: P): PathValue<T, P> | undefined {
  const parts = path.split(".");
  return parts.reduce((result, key) => {
    if (result === undefined || result === null) {
      return undefined;
    }
    return result[key as keyof typeof result];
  }, object as any) as PathValue<T, P> | undefined;
}

关键改进点说明

  • 新增PathValue类型:

    • 通过字符串字面量拆分(infer关键字),将路径拆分为当前键K和剩余路径Rest
    • 递归遍历对象结构,最终提取路径对应的目标类型
    • 处理了路径为单个键和多层嵌套两种情况
  • 调整getIn泛型:

    • 新增泛型参数P extends Path<T>,绑定路径类型和对象类型
    • 返回类型改为PathValue<T, P> | undefined,明确可能返回undefined的情况(当路径不存在或中间节点为null/undefined时)
  • 修复运行时逻辑:

    • 增加对null的判断,避免运行时错误
    • 使用类型断言处理reduce中的键类型问题,确保运行时兼容性

测试验证

interface Human {
  address: {
    city: {
      name: string;
    }
  };
  age?: number;
}

const human: Human = { address: { city: { name: "Town" } }, age: 25 };

// 返回类型为string,值为"Town"
const cityName = getIn(human, "address.city.name");
// 返回类型为{ name: string },值为{ name: "Town" }
const city = getIn(human, "address.city");
// 返回类型为number | undefined,值为25
const age = getIn(human, "age");
// 路径不存在时,返回类型为undefined
const invalid = getIn(human, "address.zip");

扩展说明

  • 递归深度:默认深度为4,可通过Path<T, 6>这样的方式调整最大嵌套深度
  • 可选属性:自动支持可选属性的路径读取,返回类型会包含undefined
  • 数组支持:如果对象包含数组,可扩展类型支持数组索引(如"users.0.name"),只需调整Path和PathValue类型对数字键的处理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 02:15:39