type Structure = {
foo: {
a: 1,
b: 2,
}
bar: {
c: 3,
d: 4,
}
}
type Values<T> = T[keyof T]
type Elem = string;
type Acc = Record<string, any>
/**
* Just like Array.prototype.reduce predicate/callback
* Receives accumulator and current element
* - if element extends one of accumulators keys -> return acc[elem]
* - otherwise return accumulator
*/
type Predicate<Accumulator extends Acc, El extends Elem> =
El extends keyof Accumulator ? Accumulator[El] : Accumulator
type Reducer<
Keys extends Elem,
Accumulator extends Acc = {}
> =
/**
* If Keys extends a string with dot
*/
Keys extends `${infer Prop}.${infer Rest}`
/**
* - Call Reducer recursively with last property
*/
? Reducer<Rest, Predicate<Accumulator, Prop>>
/**
* - Otherwise obtain wjole properrty
*/
: Keys extends `${infer Last}`
? Predicate<Accumulator, Last>
: never
{
type Test1 = Reducer<'foo.a', Structure> // 1
type Test2 = Reducer<'bar.d', Structure> // 4
}
/**
* Compute all possible property combinations
*/
type KeysUnion<T, Cache extends string = ''> =
/**
* If T extends string | number | symbol -> return Cache, this is the end
*/
T extends PropertyKey ? Cache : {
/**
* Otherwise, iterate through keys of T, because T is an object
*/
[P in keyof T]:
/**
* Check if property extends string
*/
P extends string
/**
* Check if it is the first call of this utility,
* because CAche is empty
*/
? Cache extends ''
/**
* If it is a first call,
* call recursively itself, go one level down - T[P] and initialize Cache - `${P}`
*/
? KeysUnion<T[P], `${P}`>
/**
* If it is not first call of KeysUnion and not the last
* Unionize Cache with recursive call, go one level dow and update Cache
*/
: Cache | KeysUnion<T[P], `${Cache}.${P}`>
: never
}[keyof T]
{
//"foo" | "bar" | "foo.a" | "foo.b" | "bar.c" | "bar.d"
type Test1 = KeysUnion<Structure>
}
const deepPick = <Obj,>(obj: Obj) =>
<Keys extends KeysUnion<Obj>>(keys: Keys): Reducer<Keys, Obj> =>
null as any
declare var data: Structure;
const lookup = deepPick(data)
const result = lookup('foo.a') // 1
// "foo" | "bar" | "foo.a" | "foo.b" | "bar.c" | "bar.d"
type Path = KeysUnion<Structure>
More about iteration over the tuple you can find in my article here
More explanation about typing this function you can find in my article here