首页 > 解决方案 > 如何使用联合类型深度展平 Typescript 接口并将完整的对象路径保留在键中

问题描述

假设我有以下界面:

interface Foo {
  foo: string
  bar?: number
  nested: {
    foo: string,
    deeplyNested: {
      bar?: number
    }
  }
  union: string | {
    foo: string,
    bar?: number
  }
}

现在我需要一种类型来将此接口转换为扁平版本,其中键中包含完整路径名,如下所示:

interface FooFlattened {
  foo: string
  bar?: number
  "nested.foo": string
  "nested.deeplyNested.bar"?: number

  // if union is a string:
  union: string

  // if union is an object:
  "union.foo": string
  "union.bar"?: number
}

该类型应该适用于任何级别的嵌套和任何数量的联合成员。

我已经在这里找到了一个相关的问题,但这只是一个非常基本的接口,没有联合或可选参数。

标签: typescript

解决方案


首先,我们可以创建一个类型,其中包含对象的有效虚线路径的联合。值得庆幸的是,我们已经有了一个很好的答案,在这里提供了一种方法

使用该Leaves答案中的类型,我们可以构建您的路径联合:

type DottedFooPaths = Leaves<Foo>;
// type DottedFooPaths = "foo" | "bar" | "union" | "nested.foo" | "nested.deeplyNested.bar" | "union.foo" | "union.bar"

现在我们需要使用这些路径作为键创建一个新对象,并将每个路径与原始对象中它对应的值类型相关联。为了实现这一点,我们可以反向执行点路径转换,并使用它递归地索引到我们的原始类型:

type FollowPath<T, P> = P extends `${infer U}.${infer R}` ? U extends keyof T ? FollowPath<T[U], R> : never : P extends keyof T ? T[P] : never;

// For example, this might produce:
type FooDotBar = FollowPath<Foo, "bar"> // number | undefined

然后,如果我们构造一个类型,其中每个键都是 的成员之一Leaves<Foo>,我们可以使用它FollowPath来提取相应的值类型:

type FlattenedFoo = {
    [K in Leaves<Foo>]: FollowPath<Foo, K>
}

// Produces:
// type FlattenedFoo = {
//     foo: string;
//     bar: number | undefined;
//     union: string | {
//         foo: string;
//         bar?: number | undefined;
//     };
//     "nested.foo": string;
//     "nested.deeplyNested.bar": number | undefined;
//     "union.foo": never;
//     "union.bar": never;
// }

几乎是您想要的结果,但它丢失了键上的可选修饰符;尽管我不确定是否有办法保留可选修饰符。


推荐阅读