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.
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.
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().
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
makeObject), but "tuple of objects" in the title makes me think it might be what I put in my comment. Not sure, though.