[英]Flatten an interface
我有一个用例,我希望能够基于泛型类型派生我的类型的键/字段。
例如:
这些是容器接口
export interface IUser {
id: BigInt;
name: string;
balance: number;
address: Address;
}
export interface Address {
street: string;
city: string;
zipcode: number;
tags: string[];
}
我希望能够定义执行类似操作的类型
const matcher: Matcher<IUser> = {
id: 1
}
这是我可以使用如下实现的 Matcher 来做的事情
export type Matcher<T> = {
[K in keyof T]: (number | string | boolean)
}
但是现在对于像这样的用例
const deepMatcher: Matcher<IUser> = {
id: 1,
user.address.city: 'San Francisco'
}
如何更新我的模型(匹配器)以支持此用例?
免责声明:这些类型的问题总是有很多边缘情况。 此解决方案适用于您的示例,但在我们引入可选属性或联合等内容时可能会中断。 此解决方案也不包括数组内的对象路径。
该解决方案包括两个主要部分:
首先,我们需要一个类型,它将采用类型T
并构造T
的所有路径的联合。
type AllPaths<T, P extends string = ""> = {
[K in keyof T]: T[K] extends object
? T[K] extends any[]
? `${P}${K & string}`
: AllPaths<T[K], `${P}${K & string}.`> extends infer O
? `${O & string}` | `${P}${K & string}`
: never
: `${P}${K & string}`
}[keyof T]
AllPaths
是一种递归类型。 每条路径的进度都存储在P
中。
它映射T
的所有属性并进行一些检查。 如果T[K]
是一个数组或不是一个对象,它只是将K
附加到当前路径并返回它。
T[K] extends object
? T[K] extends any[]
? `${P}${K & string}`
: /* ... */
: `${P}${K & string}`
如果T[K]
是一个对象,我们可以递归调用AllPaths
,以T[K]
作为新的T
并再次以当前键为当前路径。 这次我们还附加了一个"."
到路径,因为我们知道稍后将附加一个嵌套属性。
AllPaths<T[K], `${P}${K & string}.`> extends infer O
? `${O & string}` | `${P}${K & string}`
: never
这里的infer O
东西是一种解决方法,因此 TypeScript 不会抱怨Type instantiation is excessively deep and possibly infinite
的。 我们在这里返回递归调用的结果和当前路径,因此我们可以稍后将address
和address.street
... 作为键。
让我们在这里看看结果:
type T0 = AllPaths<IUser>
// type T0 = "id" | "name" | "balance" | "address" | "address.street" | "address.city" | "address.zipcode" | "address.tags"
我们现在需要一个类型,它采用路径并为我们获取路径的正确类型。
type PathToType<T, P extends string> = P extends keyof T
? T[P]
: P extends `${infer L}.${infer R}`
? L extends keyof T
? PathToType<T[L], R>
: never
: never
type T0 = PathToType<IUser, "address.street">
// type T0 = string
这也是一种递归类型。 这次P
作为一条完整的路径开始。
我们首先检查P
是否是keyof T
。 如果是,我们可以返回T[P]
的类型。 如果不是,我们尝试将P
拆分为两个字面量类型L
和R
,它们必须用点分隔。 如果左侧字符串文字与T
的键匹配,我们可以使用T[L]
和路径的其余部分递归调用PathToType
。
最后,我们在Matcher
类型中使用了这两种类型。
type Matcher<T> = Partial<{
[K in AllPaths<T>]: PathToType<T, K & string>
}>
我们允许AllPaths<T>
中的任何字符串作为键,并使用PathToType<T, K & string>
获取路径的相应类型。
结果如下所示:
type T0 = Matcher<IUser>
// type T0 = {
// id?: number | undefined;
// name?: string | undefined;
// balance?: number | undefined;
// address?: Address | undefined;
// "address.street"?: string | undefined;
// "address.city"?: string | undefined;
// "address.zipcode"?: number | undefined;
// "address.tags"?: string[] | undefined;
//}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.