Guide on effectively converting a table of tuples to an array of objects utility function (similar to zip) while preventing the merging of all values in typescript version 5.2.2

Almost there, but stuck on the final

TS2322: Type  TcolTuple[i]  is not assignable to type  string | number | symbol
compiler error.

Here's a nifty utility function called rowsToObjects() that many developers have probably come up with at some point, somewhat resembling the concept of zip():

const objects = rowsToObjects(
    ['id', 'color' , 'shape'   , 'size'  , 'to' ] as const,  
    [  1n, 'red'   , 'circle'  , 'big'   , '0x0'] as const,
    [  2n, 'green' , 'square'  , 'small' , '0x0'] as const,
    [  3n, 'blue'  , 'triangle', 'small' , '0x0'] as const,
)

This code results in:

[
    {id: 1n, color: 'red', shape: 'circle', size: 'big', to: '0x0'},
    {id: 2n, color: 'green', shape: 'square', size: 'small', to: '0x0'},
    {id: 3n, color: 'blue', shape: 'triangle', size: 'small', to: '0x0'},
]

The actual implementation is straightforward, but achieving correct typing is posing a challenge:

export function rowsToObjects<
    Tobj extends { [i in keyof TcolTuple as TcolTuple[i]]: TvalTuple[i] },
    TcolTuple extends readonly string[],
    TvalTuple extends { [j in keyof TcolTuple]: unknown }
>(cols: TcolTuple, ...rows: TvalTuple[]): Tobj[];

While the current code seems logically sound, the issue arises with the as TcolTuple[i] part:

TS2322: Type  TcolTuple[i]  is not assignable to type  string | number | symbol 
  Type  TcolTuple[keyof TcolTuple]  is not assignable to type  string | number | symbol 
    Type
    TcolTuple[string] | TcolTuple[number] | TcolTuple[symbol]
    is not assignable to type  string | number | symbol 
      Type  TcolTuple[string]  is not assignable to type  string | number | symbol 

Is there something obvious I'm overlooking here? The typing is almost satisfactory, but without as TcolTuple[i], it merges all values without recognizing their respective keys.

https://i.stack.imgur.com/0NOB7.png

Answer №1

It seems that the primary issue you are encountering with

{ [I in keyof TcolTuple as TcolTuple[I]]: TvalTuple[I] }

is related to the use of key remapping, which hinders the homomorphic nature of the mapped type (refer to What does "homomorphic mapped type" mean?). This results in mapping over all indices rather than solely focusing on numeric-like indices of the tuple, incorporating elements like number, creating an undesired union.

The solution is straightforward - explicitly map only over the numeric-like indices by intersecting keyof TcolTuple with the template literal type `${number}` (as introduced in microsoft/TypeScript#40598). This eliminates non-string number representations, refining the output to just

"0" | "1" | "2"
.

This modification resolves the issue:

declare function rowsToObjects<
  Tobj extends { [I in `${number}` & keyof TcolTuple as TcolTuple[I]]: TvalTuple[I] },
  TcolTuple extends readonly string[],
  TvalTuple extends { [J in keyof TcolTuple]: unknown }
>(cols: TcolTuple, ...rows: TvalTuple[]): Tobj[];

const objects = rowsToObjects(
  ['id', 'color', 'shape', 'size', 'to'] as const,
  [1n, 'red', 'circle', 'big', '0x0'] as const,
  [2n, 'green', 'square', 'small', '0x0'] as const,
  [3n, 'blue', 'triangle', 'small', '0x0'] as const,
)
/* const objects: {
    id: 1n | 2n | 3n;
    color: "red" | "green" | "blue";
    shape: "circle" | "square" | "triangle";
    size: "big" | "small";
    to: "0x0";
}[] */

If I were writing this personally, I would:

  • opt for const type parameters instead of expecting users to employ const assertions;
  • preserve tuple types of inputs to maintain order consistency between input and output arrays;
  • remove redundant generic type parameters and handle outputs directly without default type arguments reliance;
  • adopt uppercase letters for mapped type parameters like I and J to distinguish typically variable names from types.

While not critical, these adjustments yield a refined output:

declare function rowsToObjects<
  const K extends readonly PropertyKey[],
  const V extends readonly Record<keyof K, unknown>[]
>(
  cols: K, ...rows: V
): { [I in keyof V]:
    { [J in `${number}` & keyof K as K[J]]:
      V[I][J]
    }
  };

const objects = rowsToObjects(
  ['id', 'color', 'shape', 'size', 'to'],
  [1n, 'red', 'circle', 'big', '0x0'],
  [2n, 'green', 'square', 'small', '0x0'],
  [3n, 'blue', 'triangle', 'small', '0x0'],
)
/* const objects: readonly [{
    id: 1n;
    color: "red";
    shape: "circle";
    size: "big";
    to: "0x0";
}, {
    id: 2n;
    color: "green";
    shape: "square";
    size: "small";
    to: "0x0";
}, {
    id: 3n;
    color: "blue";
    shape: "triangle";
    size: "small";
    to: "0x0";
}] */

Playground link to code

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

What is the method for creating pipes that filter multiple columns?

My pipe is designed to work exclusively for the "name" column and not for the author anymore. transform(items: Book[], filter: Book): any { if (!items || !filter) { return items; } // Filter items array; keep items that match and retu ...

Exploring the functionalities of class methods within an Angular export function

Is it possible to utilize a method from an exported function defined in a class file? export function MSALInstanceFactory(): IPublicClientApplication { return new PublicClientApplication({ auth: AzureService.getConfiguration(), <-------- Com ...

What prevents us from returning Observable.of(false) in the catch block within the canActivate function?

In order to protect certain routes (admin), I utilize the canActivate feature. In this scenario, I employ an authGuard class/function: The issue arises when attempting to return an observable of boolean using: return Observable.of(false);. This approach d ...

Exploring ways to pass props in functional components in React Native

I am currently exploring how to create an API in React Native with TypeScript without using the class extends component. However, I am struggling to figure out how to access and send props from one const view to another function: App.tsx import React, {us ...

"Encountering a 'Module Not Found' error in Node.js after

Recently, I added a node-module to my project using the command: npm install typescript --save-dev However, when I tried running: tsc Windows displayed an error message indicating that "tsc" is an unknown command. Strangely, this issue does not occur o ...

What is preventing me from setting the User object to null in my Angular application?

Currently, I am working on a project in Angular and encountering a specific issue. In my service class, the structure looks like this: export class AuthService { authchange: new Subject<boolean>(); private user: User; registerUser(authD ...

Executing the Angular 2 foreach loop before the array is modified by another function

Currently, I am facing some difficulties with an array that requires alteration and re-use within a foreach loop. Below is a snippet of the code: this.selectedDepartementen.forEach(element => { this.DepID = element.ID; if (this.USERSDepIDs. ...

How can I best declare a reactive variable without a value in Vue 3 using TypeScript?

Is there a way to initialize a reactive variable without assigning it a value initially? After trying various methods, I found that using null as the initial value doesn't seem to work: const workspaceReact = reactive(null) // incorrect! Cannot pass n ...

The typescript error TS2339 is triggered by the property 'webkitURL' not being found on the 'Window' type

Currently utilizing Angular 2 in a project that is compiled with TypeScript. Encountering an issue when attempting to generate a blob image: Error TS2339: Property 'webkitURL' does not exist on type 'Window' The TypeScript code causi ...

Is there a counterpart to ES6 "Sets" in TypeScript?

I am looking to extract all the distinct properties from an array of objects. This can be done efficiently in ES6 using the spread operator along with the Set object, as shown below: var arr = [ {foo:1, bar:2}, {foo:2, bar:3}, {foo:3, bar:3} ] const un ...

Whenever I attempt to host my Node.js app using the GCP deploy command, it fails to work properly. The error message that appears states: "Module 'express' cannot be found."

My NodeJS application is written in TypeScript and utilizes the Express framework. I'm looking to host it on the GCP cloud using the gcloud app deploy command. First, I compile my TS sources to JavaScript - is this the correct approach? Afterwards, I ...

The argument labeled as 'shop' does not fit the criteria for the parameter 'items' or 'cart'

I've been doing some research but haven't had any luck finding a solution. I'm fairly new to Angular and currently working on a webstore project. I followed a tutorial, but encountered an error along the way. import { select, Store } from & ...

Prevent assigning values to rxjs observables recursively

I am seeking suggestions on how to enhance the code provided below. I will outline the issue and present my current solution, which I aim to refine. The code is written in Angular 4 (TS). herelistOfItems$: Observable<Array<Item>>; // Fetchin ...

What is the importance of using ChangeDetectorRef.detectChanges() in Angular when integrating with Stripe?

Currently learning about integrating stripe elements with Angular and I'm intrigued by the use of the onChange method that calls detectChanges() at the end. The onChange function acts as an event listener for the stripe card, checking for errors upon ...

Unexpected behavior with onKeyPress in React-Native Windows resulting in missing key press events

Currently, I am working on a Windows app using react-native version 0.54.0. For one of the functionalities, I have incorporated a TextInput element and would like to use onKeyPress. Here is my code snippet: <TextInput ref = { this.setTextInputRef } on ...

Is it possible to update the text within a button when hovering over it in Angular?

I'm looking to update the text displayed inside a button when hovering over it, similar to the examples in these images. I have already implemented the active state, but now I just need to change the text content. <button type="submit" ...

Why is webpack attempting to package up my testing files?

In my project, I have two main directories: "src" and "specs". The webpack configuration entrypoint is set to a file within the src directory. Additionally, the context of the webpack config is also set to the src directory. There is a postinstall hook in ...

"Enhance your PrimeVue Tree component with interactive action buttons placed on every TreeNode

Details: Using Vue 3.3.6 in composition API script setup style Utilizing PrimeVue 3.37.0 and PrimeFlex 3.3.1 Implemented with Typescript Objective: To create a tree structure with checkboxes that are selectable, along with action buttons on each TreeNod ...

Using Typescript to extract elements from one array and create a new array

I have a set of elements "inputData" , and it appears as follows : [{code:"11" , name= "test1" , state:"active" , flag:"stat"}, {code:"145" , name= "test2" , state:"inactive" , flag:"pass"}, {code1:"785" , name= "test3" , state:"active" , flag:"stat"}, .. ...

What is the quickest method for setting up types for node packages?

Whenever I need to use typed packages in my Node.js projects, there are two steps I have to take: Firstly, install the original package. For example: npm install express -S Secondly, install its type definition package. npm install @types/express -D I f ...