Retrieve type definitions for function parameters from an immutable array containing multiple arrays

My current challenge involves implementing a function similar to Jest's test.each iterator:

// with "as const"
forEach([
    [ 1, 2, 3 ],
    [ "a", "b", "c" ],
] as const, (first, second, third) => {
    // ...
});

// without "as const"
forEach([
    [ 1, 2, 3 ],
    [ "a", "b", "c" ],
], (first, second, third) => {
    // ...
});

The main objective here is to ensure that the arguments first, second, and third are strongly typed: without as const, they should all be of type string | number; with as const, they should be respectively 1 | "a", 2 | "b", and 3 | "c". The actual functionality of this function may not be relevant and could even be nonsensical based on its name.

I have made some progress towards achieving the desired typing effect (view in the Playground):

// implementation is not required
declare function forEach<
    Lists extends ReadonlyArray<ReadonlyArray<unknown>>,
>(
    lists: Lists,
    iterator: (...args: Lists[number]) => void,
): void;

Although I considered adopting Jest's typings, their approach seemed messy and fragile, so I decided against it.

The arguments are correctly typed, but there are still compilation errors in both scenarios:

  • with as const:

    The type readonly [1, 2, 3] is 'readonly' and cannot be assigned to the mutable type

    [first: 1 | "a", second: 2 | "b", third: 3 | "c"]

  • without as const:

    Type number[] | string[] is not assignable to type

    [first: string | number, second: string | number, third: string | number]
    . Target requires 3 element(s) but source may have fewer.

Is there a way to define the forEach function to satisfy both use cases?

Answer №1

To accurately identify the types for each case, it is necessary to infer them separately. Below is the code snippet along with explanations provided in the comments. Do let me know if further clarification is required.

declare function forEach<
    Lists extends BaseLists,
>(
    lists: Lists,
    iterator: (...args: TypeUnion<Lists>) => void,
): void;

type BaseLists = ReadonlyArray<ReadonlyArray<unknown>>;

// for readonly lists (when used with as const)
type TypeUnionReadonly<Lists extends BaseLists> = Lists[number] extends readonly [...(infer T)] // infer the tuple type for every list
    ? [...T] // tuple type
    : never

type TypeUnion<Lists extends BaseLists> = Lists[number] extends (infer T)[]
    ? T[]
    : Lists[number] extends ReadonlyArray<any>
    ? TypeUnionReadonly<Lists>
    : never

// ====================
// non const examples
forEach([
    [ 1, 2, 3],
    [ "a", "b", "c" ],
], (first, second, third) => {
    // ...
});

forEach([
    [ 1, 2, 3, 4], // works
    [ "a", "b", "c" ],
], (first, second, third) => {
    // ...
});

// ====================
// const examples
forEach([
    [ 1, 2, 3],
    [ "a", "b", "c" ],
] as const, (first, second, third) => {
   // ...
});

forEach([
    [ 1, 2, 3, 4], // error- length is 4 in one list, also the argument list has 3 parameters
    [ "a", "b", "c" ],
] as const, (first, second, third) => {
   // ...
});

forEach([
    [ 1, 2, 3, 4], // error- length is 4 in one list even if argument list has 4 parameters
    [ "a", "b", "c" ],
] as const, (first, second, third, forth) => {
   // ...
});
forEach([
    [ 1, 2, 3, 4], // works
    [ "a", "b", "c", "d"],
] as const, (first, second, third, forth) => {
   // ...
});

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 NestJS to populate data will only populate the first element

I have a Mongoose schema in NestJS structured like this: ... @Prop() casinoAmount: number; @Prop() gameHyperLink: string; @Prop() casinoHyperLink: string; @Prop({ type: Types.ObjectId, ref: 'Game' }) games: Game[]; } I'm t ...

What is the best way to find the average time in Typescript?

I am dealing with an object that contains the following properties: numberOfReturns: number = 0; returns_explanations: string [] = []; departure_time: string = ''; arrival_time: string = ''; The departure_time property hold ...

Error detected in Deno project's tsconfig.json file, spreading into other project files - yet code executes without issues?

I am working on a Deno project and need to utilize the ES2019 flatMap() method on an array. To do this, I have created a tsconfig.json file with the following configuration: { "compilerOptions": { "target": "es5", ...

Error in Nestjs Swagger: UnhandledPromiseRejectionWarning - The property `prototype` cannot be destructed from an 'undefined' or 'null' object

Currently, I am in the process of developing a Nestjs REST API project and need to integrate swagger. For reference, I followed this repository: https://github.com/nestjs/nest/tree/master/sample/11-swagger However, during the setup, I encountered the foll ...

Receiving an error in Typescript when passing an object dynamically to a React component

Encountering a typescript error while attempting to pass dynamic values to a React component: Error message: Property 'title' does not exist on type 'string'.ts(2339) import { useTranslation } from "react-i18next"; import ...

Implementing dynamic data updates for the yAxis in a chart using Highcharts and Angular 2/4

I am currently working with a spline chart: this.chart = { chart: { type: 'spline', zoomType: 'x', animation: true, marginRight: 10, renderTo ...

converting nested object structures in typescript

I'm trying to flatten a nested object in my Loopback and Typescript controller Here's the structure of my model : export class SampleModel { id: number; code: number; guide?: string; gradeData?: string; } Take a look at this example obj ...

Converting Blob to File in Electron: A step-by-step guide

Is there a way to convert a Blob into a File object in ElectronJS? I attempted the following: return new File([blob], fileName, {lastModified: new Date().getTime(), type: blob.type}); However, it appears that ElectronJs handles the File object differently ...

Develop a novel object framework by merging correlated data with identical keys

I am trying to organize the related data IOrderData by grouping them based on the productId and brandId. This will help create a new array of objects called IOrderTypeData, where the only difference between the objects is the delivery type. Each product id ...

Errors have been encountered in the Angular app when attempting to add FormControl based on the data retrieved from the backend

This specific page is a part of my Angular application. Within the ngOnInit method, I make two API calls to retrieve necessary data and iterate through it using forEach method to construct a reactive form. However, I am facing one of two different errors ...

Using JavaScript or TypeScript to locate the key and add the value to an array

My dilemma involves an object structured as follows: [{ Date: 01/11/2022, Questionnaire: [ {Title: 'Rating', Ans: '5' }, {Title: 'Comment', Ans: 'Awesome' } ] }, { Date: 01/11/2022, Questionnaire ...

implement a solution in Ionic 4 Angular 6 that triggers a function only when all the observables in a loop have

In my code, I have a function named getDevices(). This function is responsible for fetching an array of devices. After getting this array, the code iterates through each device and calls another function called updateToServer(). The purpose of updateToServ ...

What is the role of the handleSubmit parameter in React Hook Form?

I'm encountering an issue with TypeScript in the handleSubmit function. To start off, I am accessing the handleSubmit function through the useForm hook: const {handleSubmit, control, watch, reset} = useForm() Next, I define a submit function: con ...

Exploring the implementation of float type in TypeScript

Is it possible to use Number, or is there a more type-specific alternative? In the past, I have relied on Number and it has proven effective for me. For example, when defining a variable like percent:Number = 1.01... ...

What benefits do declaration files offer compared to sources in TypeScript?

When developing and releasing a library using TypeScript, there are 2 approaches: One option is to generate declaration files d.ts along with the bundled JavaScript file and then specify it in package.json with: "types": "./dist/mylib.d.ts" Alternativel ...

Step-by-step guide on incorporating an external JavaScript library into an Ionic 3 TypeScript project

As part of a project, I am tasked with creating a custom thermostat app. While I initially wanted to use Ionic for this task, I encountered some difficulty in integrating the provided API into my project. The API.js file contains all the necessary function ...

Why isn't Gzip compression working in webpack? What am I missing?

After comparing the compression results of manual webpack configuration and create-react-app for the same application, it became clear that create-react-app utilizes gzip compression, resulting in a significantly smaller final bundle size compared to manua ...

The issue with zone.js remains unresolved

Since updating to the most recent version of Angular cli, I have encountered an error when trying to run ng serve: ./node_modules/@angular-devkit/build-angular/src/webpack/es5-polyfills.js:106:0-37 - Error: Module not found: Error: Can't resolve &apo ...

Having trouble resolving TypeScript TS2322 error with Context API + useState hook in your React App?

Currently, I am working on a React Typescript project that utilizes the Context API to pass down a useState hook. export const AppContext = React.createContext(null); function App() { const [value, setValue] = React.useState(3); return ( <Ap ...

What is a Mongoose Schema type in TypeScript and how can it be used as a custom

https://i.stack.imgur.com/mtlRi.png Could anyone assist me with storing a custom object that includes attributes from the StationRating interface? ...