What is the process of converting an array of strings into property names at the type level?

Consider this function:

function invert(arr: string[]) {
  let result = {};

  arr.forEach((x, i) => {
    result[x] = i;
  })

  return result; // insert type cast here
}

I am seeking a solution to have intellisense in VSCode indicate the available properties in the resulting object. For instance,

const example = invert(['foo', 'bar', 'baz'])

Desired auto-completion output: https://i.sstatic.net/JzNs5.png

My current approach involves casting result as {[K in T[number]]: K}, where T is an alias for typeof arr. However, this method does not provide auto-completion.

How can I configure TypeScript to achieve this level of detail?

Note that the interface {[name]: number} serves as a simplified example (due to NDA constraints), while the primary focus remains on mapping array elements to property names.

Answer №1

If you choose to use as const when defining your array, here is a potential solution:

type StringToNumberMap<K extends string, A extends readonly K[]> = {[Key in A[number]]: number}

function reverseMapping<K extends string, A extends readonly K[]>(arr: A): StringToNumberMap<K, A> {
  let result: StringToNumberMap<K, A> = {} as StringToNumberMap<K, A>;

  arr
    .forEach(
      (element: K, index: number) => {
        result[element] = index;
      }
    )

  return result;
}

const sampleArray = ['apple', 'banana', 'carrot'] as const
const mappedValues = reverseMapping(sampleArray)
mappedValues. // access .apple, .banana or .carrot

Answer №2

As previously mentioned in the comments, one approach to achieve this is by making invert a generic function. Currently, arr is limited to being a string[], which is why your mapped type does not function as intended.

function invert<T extends string>(arr: T[]): Record<T, number> {
  let result = {} as Record<T, number>;

  arr.forEach((x, i) => {
    result[x] = i;
  })

  return result
}

This simple generic implementation generates a Record<T, number> where T represents the keys within the array provided.

const example = invert(['foo', 'bar', 'baz'])
// const example: Record<"foo" | "bar" | "baz", number>

Playground


While this method works, there's room for further improvement. Currently, the keys are typed simply as number, even though we can determine exactly which number corresponds to each key.

function invert<T extends string[]>(arr: [...T]): { 
    [K in keyof T & `${bigint}` as T[K] & string]: K extends `${infer N extends number}` ? N : never 
} {
  let result = {} as any;

  arr.forEach((x, i) => {
    result[x] = i;
  })

  return result
}

In this revised implementation, we directly iterate over the elements of T and filter out the number keys. Then, we convert the index K into a number.

The resulting return type is as follows:

const example = invert(['foo', 'bar', 'baz'])
// const example: {
//     foo: 0;
//     bar: 1;
//     baz: 2;
// }

Playground

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

Using Cypress with Typescript: Extracting values from aliases in cy.origin

I'm faced with a unique challenge where I need to extract data from an external source and incorporate it into my base URL. How can I remove the aliases that are causing errors whenever I try to call them? https://i.sstatic.net/gBmBW.png Below is the ...

Leveraging a Derived-Class Object Within the Base-Class to Invoke a Base-Class Function with Derived-Class Information

I have a situation where I need to access a method from a derived class in my base generic component that returns data specific to the derived class. The first issue I encountered is that I am unable to define the method as static in the abstract class! ...

Automatically compile files while performing an npm install or update

I am looking for a way to automatically compile my TypeScript code into JavaScript when another project requires it. For example, when a project runs npm install or updates with my project as a dependency, I want a specific command to be executed after all ...

The reason why Node struggles to resolve the absolute path to a directory post bundling the code using Webpack

Looking to create an AWS-CDK library using typescript and bundling it with Webpack. To include some static files from the Project root, the Copy-Plugin is used in the webpack config to move them to the dist folder. However, when referencing the imported ...

Developing a custom React component library using Webpack and Typescript, however encountering issues with Webpack consistently bundling React

I'm currently in the process of developing an external component library using Webpack and TypeScript. Although it compiles without any issues, I encounter an error when attempting to use the library: Invalid hook call. Hooks can only be called ins ...

Error in Typescript stating that the property 'children' is not found on the imported interface of type 'IntrinsicAttributes & Props'

When I try to import an interface into my Card component and extend CardProps, a yarn build (Typescript 4.5.4) displays the following error: Type error: Type '{ children: Element[]; className: string; border: true; disabled: boolean; }' is not as ...

Typescript KeyboardEvent bug: incorrect initialization of keys

I'm trying to generate a KeyboardEvent in my Typescript code. const arrowLeft = new KeyboardEvent('keydown', { key: 'ArrowLeft' }); console.log(arrowLeft.keyCode, arrowLeft.key, arrowLeft.code); When I check the value of arrowLef ...

Error: AWS.CognitoIdentityCredentials does not work as a constructor

Encountering a puzzling issue with a custom-built AWS-SDK. Perhaps it's just a case of me missing the forest for the trees, but it's driving me crazy. Here's what's happening. I constructed an SDK incorporating all Cognito and DynamoDB ...

What is causing this object to become populated only after the second call?

Here is a method that I have crafted: fetchUser(endpoint: string, email: string) { const url = this.envConfigurationService.baseEndpoint + '/' + conf.apiPrefix + '/' + endpoint + email; this.loadingService.loadingOn(); return t ...

Issues with the Array functionality causing unexpected results to be displayed

Within my Angular 4 application, I have an object named allAvailableProviders structured as such - with provider IDs 71 and 72, followed by timestamps in a 24-hour format. 71: {…} 1514678400: […] 0: 800 1: 1300 1515283200: […] 0: 800 ...

Experiencing a type error within Redux in a React Native project utilizing TypeScript

I am currently working on implementing a feature to store the boolean value of whether a phone number is verified or not. Within my login component: await dispatch(setOTPVerified(data.is_phone_verified)); Action.tsx: export const OTP_VERIFIED = 'OTP ...

At what juncture is the TypeScript compiler commonly used to generate JavaScript code?

Is typescript primarily used as a pre-code deployment tool or run-time tool in its typical applications? If it's a run-time tool, is the compiling done on the client side (which seems unlikely because of the need to send down the compiler) or on the s ...

Upgrading from Angular 2 to 4 causes compilation failure in project

Recently, I upgraded my Angular project from version 2 to version 4. The steps I followed for this upgrade are as follows: 1- Deleted the /node_modules/ folder 2- Executed the following command: npm install @angular/common@latest @angular/compiler@lat ...

What is the best way to define a Typescript type that can be either a string or an HTML element

I’m struggling to figure out how to set a Typescript type that can be either a string or an HTML element. It seems like it should be simple, but I can’t find the answer anywhere. Basically, I want to be able to pass a string or HTML as the "title" pro ...

Exploring nested JSON objects within an array using ngFor directive

My application uses Angular 6 and Firebase. I am trying to showcase a list of all appointments. Below is my approach: service.ts getRDV() { this.rdvList = this.firebase.list('/rdv'); return this.rdvList; } Model: export class RDV { key: ...

Discovering the various categories of TypeScript-compatible JavaScript libraries using an example

I am currently working on converting the following code snippet to TypeScript: import { styled } from '@mui/material/styles'; export const Logo = styled((props) => { const { variant, ...other } = props; However, I encountered an error: TS ...

An unexpected error occurred while running ng lint in an Angular project

I've encountered an issue while trying to run ng lint on my Angular project. After migrating from TSLint to ESLint, I'm getting the following error message when running ng lint: An unhandled exception occurred: Invalid lint configuration. Nothing ...

Achieve top-notch performance with an integrated iFrame feature in Angular

I am trying to find a method to determine if my iframe is causing a bottleneck and switch to a different source if necessary. Is it possible to achieve this using the Performance API? This is what I currently have in my (Angular) Frontend: <app-player ...

Removing a value from a hashmap using Typescript - what is the best way to do it?

After successfully implementing a hashmap in typescript following a helpful post, I am facing an issue with removing something from the hashmap. TypeScript hashmap/dictionary interface To add a key to the keys field of my abstract Input class's hash ...

Converting a C# Dictionary<string,string> to a TypeScript Map<string,string>

Struggling to find the best approach for handling key:value pairs in TypeScript when receiving a dictionary object from the C# backend. Everything attempted so far has not yielded the expected results. This is the C# code snippet: var displayFields = ...