I've created a type that recursively extracts indices from nested objects and organizes them into a flat, strongly-typed tuple as shown below:
type NestedRecord = Record<string, any>
type RecursiveGetIndex<
TRecord extends NestedRecord,
TPreviousIndices extends any[] = []
> = {
[K in keyof TRecord]: TRecord[K] extends NestedRecord
? RecursiveGetIndex<
TRecord[K],
AppendToTuple<TPreviousIndices,K>
>
: AppendToTuple<TPreviousIndices, K>
}[keyof TRecord]
type AppendToTuple<T extends any[], U> = [...T, U]
It functions properly when used with a specific instantiation of a nested object type, for example:
type AnIndex = RecursiveGetIndex<{ foo: { bar: { baz: number}}}>
Essentially, AnIndex
is correctly typed as
["foo", "bar", "baz"]
However, I encounter a recursion error when attempting to employ the recursive type within a function:
function doSomething<T extends NestedRecord>(arg:T){
type Nested = RecursiveGetIndex<T>
const doMore = (n: Nested) => {
console.log(n) //Type instantiation is excessively deep and possibly nested
}
}
This issue arises because TypeScript cannot predict how deeply the type T
might recurse.
Is it correct to assume this? Why doesn't TypeScript defer evaluation until after doSomething
is instantiated?
Furthermore, is there a way to prevent this error if I know beforehand that the arg
provided to the function will never surpass the TS recursion limit (which is typically 50)? Can I communicate this to TypeScript somehow?
I have seen solutions that restrict recursion by employing a decrementing array type, such as demonstrated in this Stack Overflow answer. Is this the optimal approach in this situation?
Updated
Following captain-yossarian's advice, the following modification works:
type RecursiveGetIndex<
TRecord extends NestedRecord,
TPreviousIndices extends any[] = [],
TDepth extends number = 5
> = 10 extends TPreviousIndices["length"] ? TPreviousIndices : {
[K in keyof TRecord]: TRecord[K] extends NestedRecord
? RecursiveGetIndex<
TRecord[K],
AppendToTuple<TPreviousIndices,K>
>
: AppendToTuple<TPreviousIndices, K>
}[keyof TRecord]
However, the error persists if this number exceeds 10. I assumed it should be set to 50. Could my type be more recursive than I realize?