'Typescript: return interface with keys based on array input

Here's what I'm trying to do:

Based on a string array I'd like to specify the return type as a record keyed by the strings.

e.g.

// return type -> { itemA:SomeType,itemB:SomeType }
const res = doThing(['itemA', 'itemB']) 

Is this possible?



Solution 1:[1]

Yes, you need to make doThing() generic in the string entry types of the array you pass in. It could look like this:

function doThing<K extends string>(arr: readonly K[]): { [P in K]: string } {
    return Object.fromEntries(arr.map(v => [v, "hello"])) as any;
}

Note that K extends string constrains K to be some subtype of string, and this gives the compiler a hint that you'd like it to infer string literal types for K as opposed to (the fairly useless) string. Note that the type of arr is readonly K[], a shorthand for the ReadonlyArray<K> type which, despite the name, corresponds to any array type, even those you can write to (whereas the Array<T> type corresponds to only those array types that are known to be writable. Yes, maybe it would be better to have named them differently, like PossiblyNotWritableArray<T> instead of Readonly<T>? ????). So readonly K[] is actually more permissive than K[].

The return type, {[P in K]: string} is a mapped type which iterates over the keys P in K and produces a string-valued property for each. This is equivalent to Record<K, string> using the Record<K, V> utility type.

Let's make sure it does what you want:

const res = doThing(['itemA', 'itemB']);
/*const res: {
    itemA: string;
    itemB: string;
}*/

console.log(res);
/* {
  "itemA": "hello",
  "itemB": "hello"
} */

Looks good!

Playground link to code

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 jcalz