Search code examples
typescriptgenericsstrong-typing

Using generics, how can I create a tuple of objects from a captured array?


Given a function like this:

function makeObjects<T extends string[]>(...values: T);

Make the return value this:

T.map(v => ({ [v]: any }));

I'm using an array map to show what it looks like in my mind, yes I know that's not how TS works.


Solution

  • I'd give it the following call signature:

    declare function makeObjects<T extends string[]>(...values: T):
      { [I in keyof T]: { [P in Extract<T[I], string>]: any } };
    

    ( Note that I have not implemented makeObjects(), and consider this to be outside the scope of the question as asked ).


    The return type {[I in keyof T]: ...} is a mapped type over the keys of the generic input type parameter T. When T is an array or tuple type, the output type will also be an array or tuple type.

    For each numeric-like index I from the input array type T, we want to use the element type T[I] as a key type. For this you need to use another mapped type, conceptually like {[P in T[I]]: any}, which means "an object type whose keys are T[I] and whose values are any". You could also express this as Record<T[I], any> using the Record<K, V> utility type.

    Unfortunately while you only care about numeric-like indices, the compiler takes the view that I could be any key of T, including the array method names like "push" and "pop", and thus the property type T[I] could be all kinds of things you don't want to use as keys. (See ms/TS#27995 for a discussion of this issue).

    The way to deal with that is to wrap T[I] in something the compiler will agree is definitely key-like. Since you only care about T[I] being a string (since T extends string[]), we can use the Extract<T, U> utility type to filter T[I] to just string-like things.

    So that gives you { [I in keyof T]: { [P in Extract<T[I], string>]: any }}.


    Let's test it out:

    const foo = makeObjects("a", "b", "c");
    // const foo: [{  a: any; }, { b: any; }, { c: any; }] 
    

    Looks good; the output type is a tuple of objects whose keys come from the corresponding parameter to makeObjects().

    Playground link to code