如何从 TypeScript 的界面中提取“路径表达式”?

yaq*_*awa 2 typescript recursive-type union-types

我想要实现的是:

type Post = {
  id: number
  title: string
  author: {
    name: string
  }
  comments: {
    text: string
  }[]
}

type ExtractPathExpressions<T> = ???

type Paths = ExtractPathExpressions<Post>
// expects above got a union --> 'id' | 'title' | 'author' | 'author.name' | 'comments' | `comments[${number}]` | `comments[${number}].text`
Run Code Online (Sandbox Code Playgroud)

我知道这很不寻常......但是,有人知道它会是什么样子吗ExtractPathExpressions

Ole*_*ter 8

这当然不是一项不寻常的任务,但它是一项复杂的递归任务,需要针对以下属性的不同情况进行单独处理:

  1. 是原始类型
  2. 是一个嵌套对象
  3. 是一个嵌套数组

情况 2 和 3 需要递归,因为两者都可以包含其他嵌套对象和数组。

您想要创建所有可能的路径排列的并集,因此在每一步中,我们都必须返回键本身以及连接键和属性递归结果的模板文字的并集,ExtractPathExpressions除非它是原始类型。

类型本身显然应该是映射类型(在下面的示例中,我选择了较新的键重新映射功能),其键可以在模板文字类型( 的并集string | number | bigint | boolean | null | undefined)中使用,这意味着symbol必须排除该类型。

所需的类型如下所示:

type ExtractPathExpressions<T, Sep extends string = "."> = Exclude<
  keyof {
    [P in Exclude<keyof T, symbol> as T[P] extends any[] | readonly any[]
      ?
          | P
          | `${P}[${number}]`
          | `${P}[${number}]${Sep}${Exclude<
              ExtractPathExpressions<T[P][number]>,
              keyof number | keyof string
            >}`
      : T[P] extends { [x: string]: any }
      ? `${P}${Sep}${ExtractPathExpressions<T[P]>}` | P
      : P]: string;
  },
  symbol
>;
Run Code Online (Sandbox Code Playgroud)

测试一下:

type Post = {
  id: number
  title: string
  author: {
    name: string
  }
  comments: {
    text: string,
    replies: {
        author: {
            name: string
        }
    }[],
    responses: readonly { a:boolean }[],
    ids: string[],
    refs: number[],
    accepts: readonly bigint[]
  }[]
}

type Paths = ExtractPathExpressions<Post>;
//"id" | "title" | "author" | "comments" | "author.name" | `comments[${number}]` | `comments[${number}].text` | `comments[${number}].replies` | `comments[${number}].responses` | `comments[${number}].ids` | `comments[${number}].refs` | `comments[${number}].accepts` | `comments[${number}].replies[${number}]` | `comments[${number}].replies[${number}].author` | `comments[${number}].replies[${number}].author.name` | ... 4 more ... | `comments[${number}].accepts[${number}]`
Run Code Online (Sandbox Code Playgroud)

操场