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?
Playground
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
Playground
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With