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()
.
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