扁平化类型,给出嵌套对象中所有键的联合



我觉得这应该没有那么难。然而,无论我怎么尝试,我都不能让它工作。这是目前为止我找到的最好的:

// Sample type. I want to make this into 'a' | 'b' | 'x' | 'c' | 'd'
type O = Record<'a', Record<'b' | 'x', Record<'c' | 'd', string | number>>>;
// Explicit way of doing it, with limited depth
type Explicit = keyof O | keyof O[keyof O] | keyof O[keyof O][keyof O[keyof O]];
// What I hoped would work
type ExtractKeys<T> = T extends Record<infer U, any> ?
keyof T | ExtractKeys<T[U]> :
never;
// Or
type ExtractKeys2<T> = T extends Record<string, any> ?
keyof T | ExtractKeys<T[keyof T]> :
never;
// Try it
// TS2589: Type instantiation is excessively deep and possibly infinite.
const tryIt: ExtractKeys<O> = 'a';
// Can assign anything
const tryIt2: ExtractKeys2<O> = 'z';

错误很明显,我以无限递归结束。但我真的看不出是怎么回事?我也找不到更好的办法。什么好主意吗?

游乐场

您在EtractKeys2中有一个错别字。您使用EtractKeys而不是ExtractKeys2进行递归调用。

type ExtractKeys2<T> = T extends Record<string, any> 
? keyof T | ExtractKeys2<T[keyof T]> 
: never
const tryIt2: ExtractKeys2<O> = 'z';

可以运行了


由于infer U,您的第一个方法失败了。这个检查实际上返回原语的true

type Test1 = number extends Record<string, any> ? true : false
//   ^? false
type Test2 = number extends Record<infer U, any> ? true : false
//   ^? true

由于这始终是true,因此最终会出现一个无限循环,导致类型实例化过于深入且可能无限错误。

有趣的是,你最终得到了keyof Number

type Test3 = number extends Record<infer U, any> ? U : false
//   ^? keyof Number

游乐场

根据我的理解,您希望有一个返回给定对象类型及其对象属性的所有键的类型。

如果是这种情况,下面可能是一种方法:

export type Keys<T extends object> = {
[K in keyof T]: T[K] extends object ? Keys<T[K]> | K : K
}[keyof T]
interface User {
name: {
first: string
last: string
}
}
const x: Keys<User> = 'first' // 'last', 'name'
type O = Record<'a', Record<'b' | 'x', Record<'c' | 'd', string | number>>>;
const o: Keys<O> = 'a' // 'b', 'c', 'd', 'x'

这里,我将T的每个键映射到键类型K本身。如果T[K]的值(即键的值)应该是一个对象,则创建嵌套对象的键和前键K本身的联合。

[K in keyof T]: T[K] extends object ? Keys<T[K]> | K : K

最后,使用

生成所有提到的嵌套键联合的联合:
[keyof T]

最新更新