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

Time:04-21

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.

CodePudding user response:

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

CodePudding user response:

You can use the mapped type Record to create a type from the values in T:

function makeObject<T extends string[]>(...values: T): Record<T[number], any>{
    return null!
}

let x = makeObject("a", "b", "c")

x.a
x.d // err

Playground Link

  • Related