Home > front end >  How to create object keys from array values in typescript
How to create object keys from array values in typescript

Time:09-04

How to type following function in typescript so I get autocompletion & error prevention.

Using Typescript 4.7.4

// reference fn
function doSomething(list) {

    const data = {};

    list.map(item => {
        data[item] = 'value type string|number|bool|null'   
    });

    return data;
}

// calling it like
const data = doSomething([
    'phone_number',
    'customer_email'
]);

// get IDE autocomplete here (for only properties inside data)
console.log(data.phone_number);
console.log(data.customer_email);

// typescript yell when try to access invalid properties
console.log(data.phone_numbersss);

CodePudding user response:

You can do it provided the array being used is effectively a compile-time constant at the point of the function call (so TypeScript knows what its values are). Otherwise, TypeScript doesn't know what the array contents are and will have to assume any string. There are at least two ways that constraint can be satisfied:

  1. You can say an array is constant directly by applying a constant assertion to it:

    const names = ["phone_number", "customer_email"] as const;
    // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^
    
  2. You can use an array literal at the point of the call, and use a variadic tuple type for the parameter's type (more below).

Once we know that the array's contents are a compile-time constant to TypeScript, we can use a mapped type to map an array that extends string[] to an object with the array elements as keys and the desired property types:

type DoSomethingResult<T extends readonly string[]> = {
    [Key in T[number]]: string | number | boolean | null;
};

Implementing the function requires creating the object (rather than an array) and giving the properties some value; I've chosen null:

function doSomething<T extends readonly string[]>(list: [...T]): DoSomethingResult<T> {
    // Variadic tuple type −−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^
    return Object.fromEntries(list.map((key) => [key, null])) as DoSomethingResult<T>;
}

Here's the call:

const data = doSomething([
    "phone_number",
    "customer_email"
]);

Then all of your given test cases work.

Playground link

Note that the array has to be an array literal; this won't work:

// WON'T WORK AS DESIRED
const names = [
    "phone_number",
    "customer_email"
];
const data = doSomething(names);

TypeScript infers the type of the array to be string[], not ["phone_number", "customer_email"]. (It would work if you added an as const on the array.)


If you had to support versions of TypeScript that didn't have variadic tuple types (introduced in v4.0), you'd use the as const version instead:

function doSomething<T extends readonly string[]>(list: T): DoSomethingResult<T> {
    // Using `T` directly as the type −−−−−−−−−−−−−−−−−−^
    return Object.fromEntries(list.map((key) => [key, null])) as DoSomethingResult<T>;
}
// ...
const data = doSomething([
    "phone_number",
    "customer_email"
] as const);
//^^^^^^^^

Playground link using v3.9.7

  • Related