如何从 TypeScript 的接口中提取 "path expression"?
How to extract "path expression" from an interface in TypeScript?
我想达到的效果是这样的:
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`
我知道这很不寻常...但是,有人知道 ExtractPathExpressions
会是什么样子吗?
这当然不是一项不寻常的任务,但它是一个复杂的递归任务,需要针对 属性:
的不同情况进行单独处理
- 是原始类型
- 是嵌套对象
- 是嵌套数组
案例 2 和案例 3 需要递归,因为两者都可以包含其他嵌套对象和数组。
您想创建一个 联合 所有可能的路径排列,所以在每一步,我们必须 return 键本身和模板的联合文字连接键和 属性 上递归 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
>;
正在测试:
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}]`
我想达到的效果是这样的:
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`
我知道这很不寻常...但是,有人知道 ExtractPathExpressions
会是什么样子吗?
这当然不是一项不寻常的任务,但它是一个复杂的递归任务,需要针对 属性:
的不同情况进行单独处理- 是原始类型
- 是嵌套对象
- 是嵌套数组
案例 2 和案例 3 需要递归,因为两者都可以包含其他嵌套对象和数组。
您想创建一个 联合 所有可能的路径排列,所以在每一步,我们必须 return 键本身和模板的联合文字连接键和 属性 上递归 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
>;
正在测试:
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}]`