Using TypeScript to transform types: Array of objects with keys Kn and values Vn to an object with keys Kn and values Vn

I am looking to create a function that can process tuples with a specific structure like so:

type Input = [
  { key: K1, value: V1 },
  { key: K2, value: V2 },
  { key: K3, value: V3 },
  // ...
  { key: KN, value: VN }
]

The function should then output data in the following format:

type Output = {
  [key: K1]: V1,
  [key: K2]: V2,
  [key: K3]: V3,
  // ...
  [key: KN]: VN
}

Here is an example implementation:

function fromArray(xs: Input): Output {
  const obj = {} as Output;
  for (const x of xs) {
    obj[x.key] = x.value;
  }
  return obj;
}

I am hoping to achieve compile-time inference on input and output types using code similar to this:

const geometry = fromArray([
  { key: 'circle',    value: { color: 'blue', radius: 3 } },
  { key: 'rectangle', value: { color: 'red', width: 3, height: 2 } },
  { key: 'line',      value: { length: 5 } }
])

// I would like to have autocomplete functionality here:
geometry.circle.color  // type = string
geometry.line.length   // type = number

Can TypeScript support this feature in its current version?

I am open to a complex type signature for the function "fromArray".

In case of duplicate keys in the input, I am willing to handle it by throwing an error.

Answer №1

In my opinion, the optimal way to structure this code is as follows: By utilizing KV, which represents a generic key/value object type passed within the xs argument of the fromArray() function (or its related types), we can define the output object type as KeyValToObj<T>:

type KeyValToObj<KV extends { key: PropertyKey, value: any }> =
  { [K in KV['key']]: Extract<KV, { key: K }>['value'] };

This setup ensures that when KV is a union like

{key: "a", value: string} | {key: "b", value: number}
, the resulting KeyValToObj<KV> will have keys for every unique key property present in
KV</code, with each corresponding value being retrieved from the member of <code>KV
having a matching key of type K: {a: string; b: number}.

The next step involves defining the signature of the fromArray() function:

function fromArray<XS extends Array<{ key: K, value: any }>, K extends PropertyKey>(
  xs: XS | []): KeyValToObj<XS[number]> {
  const obj = {} as any;
  for (const x of xs) {
    obj[x.key] = x.value
  }
  return obj
}

With an input of type XS, the function outputs KeyValToObj<XS[number]>, representing the object type associated with the union of element types found within XS. Utilizing a type assertion to any is necessary to address potential compiler errors due to challenges verifying the accurate output type.

To ensure proper type inference, it's crucial to include certain nuances within the signature. For example, the presence of the K type offers guidance for inferring string literal types associated with the key properties. Similarly, including | [] contributes to extracting tuple types from XS, instead of focusing on unordered arrays. These details assist in preventing unwanted behavior during typing inferred by the compiler.


Let’s put this code into action:

const geometry = fromArray([
  { key: 'circle', value: { color: 'blue', radius: 3 } },
  { key: 'rectangle', value: { color: 'red', width: 3, height: 2 } },
  { key: 'line', value: { length: 5 } }
])

geometry.circle
// Expected: { color: string; radius: number; }

geometry.circle.radius; 

geometry.line
// Expected: { length: number; }

geometry.line.length;

geometry.rectangle
// Expected: { color: string; width: number; height: number; }

geometry.rectangle.width;

The implementation seems accurate, providing the desired autocomplete functionality. Notably, in instances of a duplicate key, the output type incorporates a union of relevant value types attributed to that specific key.


I trust this explanation proves helpful to you. Best of luck with your endeavors!

Access the Playground link here

Answer №2

This method offers a degree of type safety, although it still has some margin for false positives. While any key can be paired with any value, it does ensure that the key and value are valid.

type Input<Keys, Values> = Array<{ key: Keys; value: Values }>;

type Output<Keys extends string, Values> = { [key in Keys]: Values };

function convertToObj<Keys extends string, Values>(
  arr: Input<Keys, Values>
): Output<Keys, Values> {
  const obj = {} as Output<Keys, Values>;
  for (const x of arr) {
    obj[x.key] = x.value;
  }
  return obj;
}

const geometryData = convertToObj([
  { key: "circle", value: { color: "blue", radius: 3 } },
  { key: "rectangle", value: { color: "red", width: 3, height: 2 } },
  { key: "line", value: { length: 5 } }
]);

// Valid:
geometryData.circle.color; // string
geometryData.line.length; // number
geometryData.rectangle.length; // number, should not exist
geometryData.line.color; // string, should not exist

// Invalid:
geometryData.line.background; // nonexistent property
geometryData.square; // nonexistent property

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

Is there an improved method for designing a schema?

Having 4 schemas in this example, namely Picture, Video, and Game, where each can have multiple Download instances. While this setup works well when searching downloads from the invoker side (Picture, Video, and Game), it becomes messy with multiple tables ...

What is the best way to search through an array in TypeORM?

I am working on implementing user permissions management using TypeORM with PostgreSQL. The permissions are defined within the user entity in the following column: @Column({ type: 'text', array: true }) permissions: UserPermission[] = []; Th ...

Could you tell me the kind of theme used in Material-UI version 5?

I've decided to switch my project over to MUI version 5 and embrace the use of emotion CSS. It's come to my attention that I need to incorporate the theme property, but now TypeScript is prompting me for a type definition for it. The current code ...

Having trouble with Angular 2 not properly sending POST requests?

Having some trouble with a POST request using Angular 2 HTTP? Check out the code snippet below: import { Injectable } from '@angular/core'; import { Http, Response, Headers, RequestOptions } from '@angular/http'; import 'rxjs/add ...

Is it necessary to include @types/ before each dependency in react native?

I am interested in converting my current react native application to use typescript. The instructions mention uninstalling existing dependencies and adding new ones, like so: yarn add --dev @types/jest @types/react @types/react-native @types/react-test- ...

Ways to display all current users on a single page within an application

I have come across a project requirement where I need to display the number of active users on each page. I am considering various approaches but unsure of the best practice in these scenarios. Here are a few options I am considering: 1. Using SignalR 2. ...

Issue encountered: Unable to access the property 'loadChildren' as it is undefined, while attempting to configure the path

How can I conditionally load the route path? I've attempted the code below, but it's throwing an error. Can someone guide me on how to accomplish this task? [ng] ERROR in Cannot read property 'loadChildren' of undefined [ng] i 「w ...

Merging RXJS observable outputs into a single result

In my database, I have two nodes set up: users: {user1: {uid: 'user1', name: "John"}, user2: {uid: 'user2', name: "Mario"}} homework: {user1: {homeworkAnswer: "Sample answer"}} Some users may or may not have homework assigned to them. ...

I've been stuck for hours, is there anything I should include?

I'm attempting to access http://localhost:4200/Personnes/view/:2, but I encountered the following error (ERROR TypeError: Cannot read property 'nom' of undefined) "My personnnes.service.component.ts" `export class PersonnesService { baseUr ...

Leveraging jest for handling glob imports in files

My setup involves using webpack along with the webpack-import-glob-loader to import files utilizing a glob pattern. In one of my files (src/store/resources/module.ts), I have the following line: import '../../modules/resources/providers/**/*.resource. ...

What is the best way to configure webpack for ng build instead of ng serve?

My .NET web application is hosted in IIS and it also hosts an Angular application. This setup requires both applications to be served on the same port by IIS, primarily because they share the same session cookie. Additionally, they are integral parts of th ...

Searching for nicknames in a worldwide Jest arrangement?

Before running all test cases, I need to execute certain tasks only once. To achieve this, I have created a global function and specified the globalSetup field in my Jest configuration: globalSetup: path.resolve(srcPath, 'TestUtils', 'global ...

Creating a form with multiple components in Angular 6: A step-by-step guide

I'm currently working on building a Reactive Form that spans across multiple components. Here's an example of what I have: <form [formGroup]="myForm" (ngSubmit)="onSubmitted()"> <app-names></app-names> <app-address> ...

Monitoring a Typescript Class's Get() or Set() function using Jasmine Spy

While working with Jasmine 2.9, I have encountered no issues spying on both public and private functions, except for when trying to spy on a get or set function at the class level. private class RandomService { public dogsHealth = 0; private get pers ...

Utilizing the adapter design pattern in Angular with TypeScript for enhancing a reactive form implementation

I've been struggling to understand how to implement the adapter pattern in Angular6. Despite reading numerous articles and tutorials, I still can't quite grasp the concept. Could someone provide some insights on this topic? Essentially, I have a ...

Encountering an issue with the message: "Property 'ref' is not available on the type 'IntrinsicAttributes'."

Having trouble implementing a link in React and TypeScript that scrolls to the correct component after clicking? I'm using the useRef Hook, but encountering an error: Type '{ ref: MutableRefObject<HTMLDivElement | null>; }' is not assi ...

An error occurred in the ngrx store with Angular during production build: TypeError - Unable to access property 'release' of undefined

After deploying my application and running it, I encountered an issue that seems to be happening only during production build at runtime. At this point, I am uncertain whether this is a bug or if there is a mistake in my code. The error "TypeError: Cannot ...

What is the best way to arrange map elements in a React application?

I am trying to implement filter buttons for low to high rates and high to low rates, but I am having trouble figuring it out. How can I apply the filter to display the data accordingly? The data that needs to be filtered is stored in rate.retail_rate. ty ...

Error: Unable to locate metadata for the entity "BusinessApplication"

I have been utilizing TypeORM smoothly for some time, but out of the blue, I encountered this error during an API call: EntityMetadataNotFound: No metadata for "BusinessApplication" was found. at new EntityMetadataNotFoundError (C:\Users\Rob ...

Utilizing TypeScript with Svelte Components

I've been struggling to implement <svelte:component /> with Typescript without success. Here's my current attempt: Presentation.svelte <script lang="ts"> export let slides; </script> {#each slides as slide} & ...