TypeScript - getDeepObjByKeys函数实现

1. 背景

我们有时需要从嵌套结构获取对应的数据,比如以下结构:

const nested = {
  a: {
    b: {
      c: 10,
    },
  },
};

通过a.b.c读取,但如果某个字段的值可能为空,会导致报错

我们可以实现一个getDeepObjByKeys方法去解决

getDeepObjByKeys(data,path,defaultValue)
getDeepObjByKeys(nested,"a.b.d","defaultValue")

// 读取不到对应的数据,返回 "defaultValue"

getDeepObjByKeys(nested,"a.b.c","defaultValue")
// 读取到对应的数据,返回 10
参数名说明默认值
data需要获取值的对象-
path对象路径,键名通过'.'进行连接-
defaultValue默认值undefined

2. 实现

2.1 功能实现

功能的实现,主要通过循环去操作

将path拆分为数组,进行遍历,遍历过程访问当前key对应的value,一旦value为null或undefined,结束遍历,返回默认值。否则继续遍历。

function isUndefinedOrNull(value) {
  return typeof value === "undefined" || value === null;
}

function getDeepObjByKeys(data, path, defaultValue = undefined) {
  const keys = path.split(".");
  let current = data;
  for (const key of keys) {
    if (isUndefinedOrNull(current) || isUndefinedOrNull(current[key])) {
      return defaultValue;
    }
    current = current[key];
  }
  return current;
}

功能的实现其实不难,但我们希望通过ts去约束path的类型,让编辑器能否给出自动提示,实现类似效果:

2.2 TypeScript 类型实现

先实现一个ObjPath类型,接受一个泛型作为参数,约束泛型的类型为object。

泛型计算后,返回的类型是由object的key拼接的类型,是一个字符串的联合类型。

实现如下:

type ObjPath<Data extends object> = {
  [Key in keyof Data & (string | number)]: Data[Key] extends object
    ? `${Key}` | `${Key}.${ObjPath<Data[Key]>}`
    : `${Key}`;
}[keyof Data & (string | number)];

遍历键值对,判断值的类型是否为object,如果是,递归。

实现没有什么难度,但需要约束keyof Data的类型为number或者string。

以及[keyof Data & (string | number)]去约束递归的时候,每一层keyof都是number或者string。

调整我们的代码实现:

function isUndefinedOrNull(value: unknown) {
  return typeof value === "undefined" || value === null;
}

function getDeepObjByKeys<T extends object, U = unknown>(
  data: T,
  path: ObjPath<T>,
  defaultValue?: U
) {
  const keys = path.split(".");
  let current: Record<string, any> = data;
  for (const key of keys) {
     if (isUndefinedOrNull(current) || isUndefinedOrNull(current[key])) {
      return defaultValue;
    }
    current = current[key];
  }
  return current;
}