I'm trying to make a helper function that takes a nested object like JSON, and allows one to make a deep copy of a nested value at any arbitrary depth. I understand variadic tuple types and can get them to work for just passing around the tuples - but I don't know how to 'map' them to nested Picks of arbitrary depth (it may not even be possible). Here's the best I've come up with - but is still limited to needing to create as many overloads for GetNestedValue as I would care to support. I understand the various errors, I just can't come up with any way to satisfy the compiler and get type completion on the return value.
// K is arbitrary length how to express N accessors deep? in TS without a loop?
type GetNestedValue<K extends string[], O extends any> = O[K[0]][K[1]][K[2]];
function getNestedItem<Keys extends string[], Obj>(
obj: Obj, ...keys: readonly [...Keys]
): GetNestedValue<Keys, Obj> extends undefined ? undefined : GetNestedValue<Keys, Obj> {
let level: any = obj;
for (const key of keys) {
if (level !== undefined) {
level = level[key];
} else {
return;
}
}
// this will return deepClone(level);
return level;
}
const obj = {one: 1, two: {three: {four: 4}}};
// I'd prefer 'known' shapes of obj here block form entering invalid keys.
const a = getNestedItem(obj, 'one', 'two');
// here - when arbitrarily trying to grab stuff from unknown inputs - I don't want
// a warning, rather the user would just need to check `if (b !== undefined)`
const b = getNestedItem(obj as any, 'one', 'two');
link to playground
I'll lead off by saying: While a fun thought experiment, I wouldn't recommend this due to the amount of recursion it requires.
It requires two recursive types, A type to get a valid set of keys inferred from an object type, and a getter to access the property given those validated keys. For TypeScript < 4.5, the depth limit will be a tuple of length 10.
Validation:
Getter:
In order for a function to correctly infer a generic, that generic needs to be present as a possible argument. What we want is
ValidKeys, but we can't do that without havingKeysitself as a potential argument. So we make use of a conditional for the...keysargument to force it to resolve.Regarding the return type, Even though
GetNestedPropwill potentially be a union withundefined, the compiler can't infer that it definitely is in the case where your else branch is hit. So you can either make the return type this awkward conditional, or//@ts-expect-errorthe else branch return statement with a simpler return type ofGetNestedProp<Keys, Obj>. That alternative is included in the playground:Given a type with an optional property, digging into that property will convert the nested property type into a union with undefined:
playground