我想提取所有深度嵌套的键,包括数组中的键,但不包括中间索引访问:
type Pack = {
name: string
items: string[]
}
interface Params {
name: string
items: string[]
pack: Pack
packs: Pack[]
nested1: {
nested2: {
nested3: string
}[]
}
}
type ParamKeys = <...> // expected: 'name' | 'items' | 'pack' | 'pack.name' | 'pack.items'| 'packs' | 'packs.name' | 'packs.items' | 'nested1' | 'nested1.nested2'| 'nested1.nested2.nested3'
我知道这个问题已经被多次回答,但所有解决方案都需要数组索引,例如
packs.8.name
,因为密钥被推断为 packs.${number}.name
。就我而言,我希望用户构造一条适用于所有数组元素的选项记录,因此索引并不是真正有用,并且将数组中的嵌套元素作为必需的键排除在外,这不是我想要的。另一个不太理想的解决方案是,如果上述方法不可行,则将索引显式输入为零,即 nested1.nested2.0.nested3
作为必需键,例如
算法:
T
是某种我们不想通过 return 更深入的类型 ''
T
是一个数组,调用 T
T
的字符串键进行映射,其中 K
是 T
的键:
T[K]
是某种我们不想通过 return 更深入的类型 K
T[K]
是我们想要忽略的某种类型,则返回 never
K
的并集,并对 T[K]
进行递归检查,前缀为 ${K}.
(假设在这一步我们只处理对象)实施:
首先,我们要停止的类型,主要是原语:
type StopTypes = number | string | boolean | symbol | bigint | Date;
要忽略的类型:
type ExcludedTypes = (...args: any[]) => any;
允许我们创建
.
符号的类型。如果第二个参数是空字符串,则返回第一个参数,不带尾随 .
:
type Dot<T extends string, U extends string> = '' extends U ? T : `${T}.${U}`;
type GetKeys<T> = T extends StopTypes
? ''
: T extends readonly unknown[]
? GetKeys<T[number]>
: {
[K in keyof T & string]: T[K] extends StopTypes
? K
: T[K] extends ExcludedTypes
? never
: K | Dot<K, GetKeys<T[K]>>;
}[keyof T & string];
我们使用索引访问来获取数组类型元素的类型,并使用映射类型来映射对象的键
测试:
type Pack = {
name: string;
items: string[];
};
interface Params {
name: string;
items: string[];
pack: Pack;
packs: Pack[];
fn: (...args: any) => any;
nested1: {
nested2: {
nested3: string;
}[];
};
}
// "name" | "items" | "pack" | "packs" | "nested1" | "pack.name" | "pack.items" | "packs.name" | "packs.items" | "nested1.nested2" | "nested1.nested2.nested3"
type ParamKeys = GetKeys<Params>;
基于 @wonderflame 的回答,如果我们有一个更简单的用例,不需要嵌套数组(例如,跳过
packs.name
,因为 packs 是一个数组),这可能是一个解决方案。
type Dot<T extends string, U extends string> = U extends '' ? T : `${T}.${U}`;
type NestedKeysOf<T> = T extends Array<any>
? never
: T extends Record<string, any>
? {
[K in keyof T & string]: K | Dot<K, NestedKeysOf<T[K]>>;
}[keyof T & string]
: '';
这将导致以下字段:
"name" | "items" | "pack" | "packs" | "nested1" | "pack.name" | "pack.items" | "nested1.nested2"