Converting a constant array of objects in Typescript into a typed index lookup object

const vehicles = [
  { id: 'id1', name: 'bike' },
  { id: 'id2', name: 'scooter' },
  { id: 'id3', name: 'motorcycle' },
] as const;
type VehicleId = typeof vehicles[number]['id'];

const vehicleIndexes = vehicles.reduce((acc, vehicle, i) => ({ ...acc, [vehicle.id]: i }), {});
console.log(vehicleIndexes);

This piece of code is functioning correctly and displays

{ id1: 0, id2: 1, id3: 2 }

vehicleIndexes provides a map enabling the retrieval of each vehicle's index by their id, however, I desire for vehicleIndexes to be strictly typed. Upon modifying its type definition to { [K in VehicleId]: number }:

const vehicleIndexes: { [K in VehicleId]: number } = vehicles.reduce((acc, vehicle, i) => ({ ...acc, [vehicle.id]: i }), {});

An error occurs:

error TS2739: Type '{}' is missing the following properties from type '{ id1: number; id2: number; id3: number; }': id1, id2, id3

I prefer using a plain object over a Map, since an object with the type { [K in VehicleId]: number } ensures that the search for the index cannot fail for any VehicleId.

Answer №1

As far as I know, unless you explicitly cast to {[K in CarId]: number}, TypeScript does not have the capability to automatically detect that

cars.reduce((acc, car, i) => ({ ...acc, [car.id]: i }), {})
(or similar methods) will produce an object with CarId as the index.

Answer №2

In my opinion, it is necessary to explicitly specify the accumulator value for the reduce method:

const vehicles = [
    { id: 'id1', type: 'car' },
    { id: 'id2', type: 'truck' },
    { id: 'id3', type: 'suv' },
] as const;
type VehicleId = typeof vehicles[number]['id'];

const vehicleIndexes = vehicles
    .reduce(
        (acc, vehicle, i) => (
            { ...acc, [vehicle.id]: i }
        ),
        {} as { [K in VehicleId]: number }
    );
console.log(vehicleIndexes);

Answer №3

An effective way to ensure type safety in this scenario is to utilize the Partial type:

let carIndexes: Partial<{ [key in CarId]: number }> = {};
for (let i = 0; i < cars.length; ++i) {
  carIndexes[cars[i].id] = i;
  
  // The code below will not compile due to TS2339: Property 'OTHER_PROPERTY' does not exist on type 'Partial<{ id1: number; id2: number; id3: number; }>'
  // carIndexes.OTHER_PROPERTY = 0;
}

In contrast, using the reduce() function with an explicit cast, as suggested by other responses, provides little static type safety. For example, even if I mistakenly write the following, the code will still compile:

const carIndexes: { [K in CarId]: number } = cars.reduce(
  (acc, car, i) => ({ 
    ...acc,
    // This line should not be valid since there is no property "id" in typeof carIndexes
    id: i 
  }),
  {} as {[K in CarId]: number}
);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Having trouble dynamically rendering a Component using Object.entries

Looking to streamline my code, I am trying to iterate over a series of MUI rows using a loop and Object.entries. However, when attempting to extract the value of each separate Object, I encounter the following error: TS7053: Element implicitly has an &ap ...

What is the most effective method for merging two arrays in JavaScript?

Can a function be created to generate an array like the following? ["0-AA", "0-BB", "1-AA", "1-BB", "2-AA", "2-BB", "3-AA", "3-BB"] This particular function combines two array ...

Grouping Columns in an HTML Table using Angular 4

I'm currently faced with the task of retrieving flat data from an API and presenting it in an HTML table using Angular 4. I'm a bit unsure about how to iterate over the data, possibly using a for-each loop. I have attempted to utilize ngFor but I ...

Optimizing an ASP.NET web application for seamless integration with Angular 2 and TypeScript

For the past couple of days, I have been delving into angular2. I am curious to understand the process of configuring my project to effectively utilize angular2 and typescript. My development environment is Visual Studio 2015. Can you guide me on the nec ...

"Upon clicking the login button, I encountered an issue with redirecting to the destination

I've been working on developing a login page using Angular framework. However, I'm facing an issue where I am unable to access the destination page after entering the login credentials. Below, you can find a snippet of the code from the login.com ...

What is the best way to retrieve values from a multi-dimensional array?

I am working with Angular and have an array named testUsers that contains sample data as shown below: this.testUsers = [ {email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="94a5d4f2f5fff1baf7fbf9">[email pr ...

Type guards do not work properly on a union of enum types in TypeScript

Recently delved into the concept of Type Guards Chapter within the realm of Typescript However, I encountered an issue where my basic type guards failed to differentiate a union of enums. Why is this happening? enum A { COMMA = ',', PLUS = & ...

What is the most efficient way to execute useEffect when only one specific dependency changes among multiple dependencies?

My main objective is to update a state array only when a specific state (loadingStatus) undergoes a change. Yet, if I include solely loadingStatus as a dependency, React throws an error requesting all dependencies [loadingStatus, message, messageArray, set ...

Prevent data loss on webpage refresh by using Angular's local storage feature

As a beginner in Angular, I am exploring ways to retain user input and interactions on my webpage even after a refresh. After some research, I came across using local storage as a viable solution. A different answer suggested utilizing the following code s ...

What is the correct way to implement the Maybe functor in typing?

I've been working on implementing a Maybe functor, inspired by Dr. Frisby's approach. Here is my code: interface Maybe<T> { isNothing: () => boolean; map: <S>(fn: (x: T) => S) => T extends null | undefined ? Maybe<S& ...

Issue with Redis cache time-to-live not adhering to set expiration

I have encountered an issue while using IoRedis and DragonflyDB to implement rate limiting in my web application. Despite setting a TTL of 5 seconds for the keys in the Redis DB, sometimes they do not expire as expected. I am struggling to understand why t ...

Retrieve parent route parameters from a dynamically loaded route component

Struggling to access the parent route params in a lazy loaded route component using activatedRoute.parent.params. Despite this not working, I have managed to find a solution that involves fetching the value using an array index number which feels like a &a ...

The `sonarqube-scanner@^4.0.0` does not produce a non-zero exit code when a Quality Gate failure occurs

Latest SonarQube: Developer Edition v10.5.1 (90531) Sonarqube-scanner version: 4.0.0 or 4.0.1 Utilized npm package: https://www.npmjs.com/package/sonarqube-scanner Node.js version: 20.14 Upon executing the following command: npx sonarqube-scanner@^4.0.0 - ...

Revamping Angular Material Table - A guide to refreshing column headers in a pre-existing table

I am currently working on an Angular 7 project where I have implemented a basic component that utilizes an Angular Material Table. I want to be able to update both the data and the header of the table when a certain event occurs, such as a mouse click. Be ...

Converting a string value into an object in Javascript using a command that functions similarly to eval in Python

When working with Python, the stringValue variable is often assigned as a string: stringValue = '{"DATA":{"VERSION":1.1, "STATE":True, "STATUS":"ONLINE"}}' To convert this string into a Python di ...

Locating a class variable using a string chosen from a DropDown menu

In my Tv class, I have several string variables. One requirement is for the user to select an option from a DropDown list and input a value. This entered value should then be stored in the Tv class under a variable with a similar name to the selected optio ...

Angular 2: Obtaining the caret position in a contenteditable 'div'

Take a look at this code snippet: <div #test (click)="onClick(test)" contenteditable="true"> This text can be edited by the user. </div> @ViewChild('test') el:ElementRef; constructor(private elementRef: ElementRef) {} ...

Multiple consecutive requests within another (Angular)

Currently, I am deepening my understanding of rxjs and struggling to find an efficient way to manage a sequence of requests. For instance, let's consider the UserService where one of its functions retrieves a user object based on the user id. After ob ...

Fixed-sized array containing union parameters

One of my programming utilities allows me to create a statically sized array: export type StaticArray<T, L extends number, R extends any[] = []> = R extends { length: L; } ? R : StaticArray<T, L, [...R, T]>; To verify its functionality, ...

How to invoke a method in a nested Angular 2 component

Previous solutions to my issue are outdated. I have a header component import { Component, OnInit } from '@angular/core'; import { Router, NavigationEnd } from '@angular/router'; import { Observable } from 'rxjs/Observable'; ...