This particular case presents an intriguing scenario. To gather all keys from both the main and nested objects, a recursive approach is necessary, leveraging the availability of recursive types in TypeScript. However, as we delve deeper into recursion, a potential challenge arises - specifically, the issue of "Type instantiation is excessively deep and possibly infinite." This can be problematic since the depth of the object remains unknown, prompting further exploration of this concern through the provided link to gain a better understanding.
To facilitate the gathering process effectively, imposing a limit on recursion becomes imperative. In this instance, a predefined limit of 5 has been set, offering room for expansion by replicating the established pattern.
Initially, defining the type:
type Level = 0 | 1 | 2 | 3 | 4 | 5 | 'max'; // addresses the infinite loop error
type NextLevel<Level> =
Level extends 0 ? 1
: Level extends 1 ? 2
: Level extends 2 ? 3
: Level extends 3 ? 4
: Level extends 4 ? 5
: 'max'; // enables iteration from 0 to 5, concluding with 'max'
type NestedKeyof<T, L extends Level = 0> = L extends 'max' ? never : {
[K in keyof T]: T[K] extends object ? K | NestedKeyof<T[K], NextLevel<L>> : K
}[keyof T]
The NestedKeyof
type serves as a mapped type, consolidating all keys within the map while extending the gathering process to nested objects when encountered. Notably, the recursive passage of the object T[K]
alongside the incremental level adjustment provided by NextLevel<L>
ensures holistic key extraction. The iterative journey concludes with the utilization of the 'max' type heralded by the initial declaration: L extends 'max' ? never
.
A simple evaluation test showcases the accuracy of the type:
type NestedType = NestedKeyof<{ a: { b: 'hey', c: { d: 'elo' } } }>; // a | b | c | d
Subsequently, integrating it within your function follows suit:
function get<T, P extends NestedKeyof<T>>(obj: T, props: P[] | keyof T): any {
const toArray = coereceArray(props);
return obj && toArray.reduce(
(result, prop) => result == null ? undefined : result[prop] as any,
obj
);
}
const result = get({ a: { b: 'hey' } }, ['a', 'b']); // correct
const result2 = get({ a: { b: 'hey' } }, 'a'); // correct
const result3 = get({ a: { b: 'hey' } }, ['a', 'b', 'c']); // expected error
While the NestedKeyof
type operates efficiently within its designated constraints, there exist limitations due to the self-imposed cap delineated by Level
and
NextLevel</code, catering up to 5 levels of nested objects. For intricate structures surpassing this threshold such as:</p>
<pre><code>type NestedTypeAboveMax = NestedKeyof
<{ a: { b: 'hey', c: { d: 'elo', e: { f: { g: { h: {i: 'test'}} } } } } }>;
Notably, the key i
falls outside the specified limit and thus isn't considered. Should more extensive nested hierarchies be explored, contemplating extensions to Level
and NextLevel
becomes requisite to accommodate additional levels seamlessly.