Eliminate nevers to make union possible

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>

Playground

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

Leave a Comment