Suppose I have:
interface JaggedRecord {
[k: string]: string | JaggedRecord;
}
const MyKeys = {
SubKey1: {
child1: 'child1',
child2: 'child2',
},
SubKey2: {
child3: 'child3',
child4: 'child4',
SubKey3: {
child5: 'child5',
},
},
child6: 'child6',
} as const;
// obviously order doesn't matter
type AllValues = 'child1' | 'child2' | 'child3' | 'child4' | 'child5' | 'child6';
Now I want to create a type which dynamically generates AllValues.
Ever the effective rubber duck, Stack Overflow forced me to figure out the answer in order to write the question.
Here you have a bit simplified solution:
const MyKeys = {
SubKey1: {
child1: 'child1',
child2: 'child2',
},
SubKey2: {
child3: 'child3',
child4: 'child4',
SubKey3: {
child5: 'child5',
},
},
child6: 'child6',
} as const;
type All<T> = {
[P in keyof T]: T[P] extends string ? P : All<T[P]>
}[keyof T]
type Result = All<typeof MyKeys> // 'child1' | 'child2' | 'child3' | 'child4' | 'child5' | 'child6';
The trick, of course, turned out to be abstraction that made it easier to reason about what I was retrieving.
type JaggedRecordKeys<T extends JaggedRecord> = {
[K in keyof T]: T[K] extends JaggedRecord ? [K, T[K]] : never;
}[keyof T];
type ExtractAllValues<T extends JaggedRecord> =
| { [K in keyof T]: T[K] extends string ? T[K] : never }[keyof T] & string
| { [K in JaggedRecordKeys<T>[0]]: ExtractAllValues<Extract<JaggedRecordKeys<T>, [K, any]>[1]>; }[JaggedRecordKeys<T>[0]];
type AllValues = ExtractAllValues<typeof MyKeys>;
First we create a type (JaggedRecordKeys) that takes the keys of T whose value is not a string, then extracts a union of tuples of that key and that key's value.
Then, we make ExtractAllValues return all the keys of T whose value is a string unioned with the recursive call of every extracted value of T's keys whose value is a JaggedRecord, and dereference that recursive call's return value.
I suspect I could make JaggedRecord generic to allow any type of child, but when I tried it, ExtractAllValues said it had a circular reference. Since my use-case is specifically for strings, I left it as-is.
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