I feel like this should not be so difficult. Yet, no matter what I try, I can't get it to work. Here's the best I got so far:
// 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';
The error is quite clear, I'm somehow ending up with infinite recursion. Yet I really do not see how? Nor do I find a better way. Any ideas?
CodePudding user response:
You had a typo in EtractKeys2
. You used EtractKeys
instead of ExtractKeys2
for your recursive call.
type ExtractKeys2<T> = T extends Record<string, any>
? keyof T | ExtractKeys2<T[keyof T]>
: never
const tryIt2: ExtractKeys2<O> = 'z';
This works now.
Your first method failed because of the infer U
. This check actually returns true
for primitives.
type Test1 = number extends Record<string, any> ? true : false
// ^? false
type Test2 = number extends Record<infer U, any> ? true : false
// ^? true
Since this is always true
, you end up with an infinite loop causing the "Type instantiation is excessively deep and possibly infinite" error.
Interestingly you end up with keyof Number
with this inference.
type Test3 = number extends Record<infer U, any> ? U : false
// ^? keyof Number
CodePudding user response:
As I understand it, you want to have a type that returns all the keys of the given object type and its object properties.
If this is the case, the following could be a possible approach:
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'
Explanation
Here, I map each key of T
to the key type K
itself. If the value of T[K]
, which is the key's value, should be an object, I create a union of the nested object's keys and the former key K
itself.
[K in keyof T]: T[K] extends object ? Keys<T[K]> | K : K
Last, I generate a union of all those mentioned nested key unions using:
[keyof T]