TypeScript 从复杂对象构建联合类型

TypeScript build union type from complex object

我有以下 object:

const object = {
  root: {
    path: 'path1',
  },
  paths: [
    {
      path: 'path2',
    },
    {
      path: 'path3',
    }
  ],
};

我想构建一个如下所示的联合类型:

type extractedPaths = 'path1' | 'path2' | 'path3';

有没有办法用 TypeScript 实现这个?

不确定你想在这里实现什么但是例如你可以有类似的东西(检查打字稿playground

type ExtractedPath = 'path1' | 'path2' | 'path3'
type ExtractedObjectPath = {
    path: ExtractedPath

}
type YourObject = {
    root: {
        path: ExtractedPath
    },
    paths: ExtractedObjectPath[]
}

const myObject: YourObject = {
  root: {
    path: 'path1',
  },
  paths: [
    {
      path: 'path2',
    },
    {
      path: 'path3',
    }
  ],
};

首先,您需要更改 object 的初始化,以便编译器知道它应该关心 literal types of the string-valued properties. Otherwise you'll get a value of type {root:{path:string},paths:{path:string}[]} and then no matter what you do you'll get just string out. So let's use a const assertion 以获得更具体的 object 类型:

const object = {
  root: {
    path: 'path1',
  },
  paths: [
    {
      path: 'path2',
    },
    {
      path: 'path3',
    }
  ],
} as const;

这给了我们以下类型:

/* const object: {
    readonly root: { readonly path: "path1"; };
    readonly paths: readonly [
      { readonly path: "path2"; }, 
      { readonly path: "path3"; }
    ]; 
  } */

很好,编译器知道 "path1""path2""path3"


然后您将需要确定如何识别您关心的类型。一种可能性是通过 object 的类型递归下降并构建一个 union of any string literal property value types you find along the way. Here's a recursive conditional type 来执行此操作:

type DeepStringLiteralValues<T> =
  T extends string ? string extends T ? never : T :
  T extends readonly any[] ? DeepStringLiteralValues<T[number]> :
  T extends object ? { [K in keyof T]-?: DeepStringLiteralValues<T[K]> }[keyof T] : never;

对于任何类型 T,如果 T 本身是字符串文字类型,那么 DeepStringLiteralValues<T> 将只是 T。否则,如果它是类似数组的类型,我们会为数组的每个元素类型计算 DeepStringLiteralValues<T[number]>。否则,如果它是类对象类型,我们会为 keyof T 中的每个键 K 收集 DeepStringLiteralValues<T[K]> 的并集。否则,它不是字符串或数组或对象,所以我们忽略它并 return never。让我们看看它做了什么:

type ExtractedPaths = DeepStringLiteralValues<typeof object>
// type ExtractedPaths = "path1" | "path2" | "path3"

看起来不错。


也可以想象,您的意图只是使用名为 "path" 的键获得所有递归属性的并集。如果是这样,您可以改为这样做:

type DeepPropKey<T, K extends PropertyKey> =
  K extends keyof T ? T[K] :
  T extends readonly any[] ? DeepPropKey<T[number], K> :
  T extends object ? { [P in keyof T]-?: DeepPropKey<T[P], K> }[keyof T] :
  never;

它与其他版本类似,但 DeepPropKey<T, K> 试图为具有键 K 的属性查找 属性 值类型,而不是获取它可以找到的任何字符串文字。对于您的示例,它会产生相同的输出:

type ExtractedPaths = DeepPropKey<typeof object, "path">
// type ExtractedPaths = "path1" | "path2" | "path3"

Playground link to code