Using TypeScript to create a unique object type that is mapped with generics for each key

Suppose I have a type representing an array of two items: a list of parameters and a function with arguments corresponding to the parameters. I've created a handy function to infer the generic type for easy use.

type MapParamsToFunction<A extends any[]> = [[...params: A], (...args: A) => void]

function asMap<A extends any[]>(map: MapParamsToFunction<A>): MapParamsToFunction<A> {
    return map;
}

asMap([[],() => {}]) // good
asMap([[0, "Hello World", 2], (a: number, b: string, c: number) => { }]); // good
asMap([[0, "Hello World", 2], (a: number, b: number, c: number) => { }]); // error
asMap([[0, "Hello World"], (a: number, b: string, c: number) => { }]); // error

Everything is working fine so far. Now, I want to extend this concept and create a dictionary where each key can contain a different set of parameters/arguments. But, I'm facing a challenge in getting TypeScript to allow a unique generic type for each key.

I attempted using any[] in the type definition, but it doesn't provide a type error if the parameters and arguments don't match.

type FunctionDictionary<T extends string> = {
    [K in T]: MapParamsToFunction<any[]>
}

function asDictionary<T extends string>(dict: FunctionDictionary<T>): FunctionDictionary<T> {
    return dict;
}

let obj = asDictionary({
    "foo": [[0, "Hello World", 2], (a: number, b: number, c: number) => { }], // no type error
    "bar": [["","",""], (a: string, b: string, c: string) => { }]
});

Is there a way to achieve individual generic parameter lists for each argument in the mapping?

type FunctionDictionary<T extends string> = {
    [K in T]: MapParamsToFunction<?> // <--- that's the puzzle
}

Answer №1

Unfortunately, TypeScript does not support existential types to specify "a MapParamsToFunction<X> for some type X". Therefore, you cannot simply make FunctionDictionary a record of those types. However, TypeScript does offer inference from mapped types, allowing you to take an object type T whose properties consist of argument lists and map each property K to an appropriate MapParamsToFunction<T[K]>:

type FunctionDictionary<T extends Record<keyof T, any[]>> = {
    [K in keyof T]: MapParamsToFunction<T[K]>
}

You can then use your asDictionary() function to infer the T object from a passed-in FunctionDictionary<T> like this:

function asDictionary<T extends Record<keyof T, any[]>>(
    dict: FunctionDictionary<T>
): FunctionDictionary<T> {
    return dict;
}

This approach will provide you with the desired behavior:

let obj = asDictionary({
    "foo": [[0, "Hello World", 2], (a: number, b: number, c: number) => { }], // error!
    // --------------------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Types of parameters 'b' and 'args_1' are incompatible.
    "bar": [["", "", ""], (a: string, b: string, c: string) => { }]
});

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

Ways to display two tabs in reactjs

My typical app is running smoothly, but I am trying to implement a feature where it launches two tabs upon opening. The first tab should open with a normal path of '/' and the second one with a path of '/board'. Here's an example: ...

Define a new type in Typescript that is equal to another type, but with the added flexibility of having optional

I have 2 categories: category Main = { x: boolean; y: number; z: string } category MainOptions = { x?: boolean; y?: number; z?: string; } In this scenario, MainOptions is designed to include some, none, or all of the attributes that belong to ...

Reactive property in the Vue composition API

Within a Vue 3 project that utilizes TypeScript, there are two properties named locale and content: <script setup lang="ts"> import { computed, ref } from 'vue' import { useI18n } from "vue-i18n" import { Landing, Local ...

What is the issue with this asynchronous function?

async getListOfFiles(){ if(this.service.wd == '') { await getBasic((this.service.wd)); } else { await getBasic(('/'+this.service.wd)); } this.files = await JSON.parse(localStorage.getItem('FILENAMES')); var ...

Convert an object to nested JSON in Angular 5

I am struggling with using Angular 5 HttpClient to send a post request because I am having trouble casting an object to nested JSON. For instance, I have the following class: export class Team { members: Person[]; constructor(members: Person[]) ...

When it comes to TypeScript, one can always rely on either event.target or event

I'm a beginner with TypeScript and currently in the process of refactoring an arrow function within React.js. Here is the current implementation: handleChange = (event): void => { const target = event.target || event.srcElement; this.s ...

Steps to activate a button once the input field has been completed

https://i.sstatic.net/l6mUu.png It's noticeable that the send offer button is currently disabled, I am looking to enable it only when both input fields are filled. Below, I have shared my code base with you. Please review the code and make necessar ...

When trying to pass 3 parameters from an Angular frontend to a C# MVC backend, I noticed that the server side was receiving null

I have encountered an issue where I am attempting to pass 3 parameters (2 types and one string) but they are showing up as null on the server side. Below is my service: const httpOptions = { headers: new HttpHeaders({ 'Content-Type&ap ...

What is the best way to iterate through an array of arrays using a foreach loop to calculate the total number of specific properties?

For instance, if YieldCalcValues were to look something like this: [ [ 850, 500 ], [ 3, 6 ], [ 1200, 5000 ], [ 526170, 526170 ] ] I am looking to create a foreach loop that calculates the yield per for each product. How can I accomplish this correctly? l ...

Drizzle-ORM provides the count of items in a findMany query result

Hello there, I'm currently experimenting with the Drizzle ORM and imagine I have this specific query const members = await trx.query.memberTable.findMany({ with: { comments:true } }) I'm wondering how I can retrieve the total count of me ...

Encountering unproductive errors with custom editor extension in VS Code

I'm currently developing a custom vscode extension for a read-only editor. During testing, I encountered a rather unhelpful error message. Can anyone offer some insight on what might be causing this issue? 2022-11-25 11:36:17.198 [error] Activating ex ...

What is the best way to incorporate additional data into a TypeScript object that is structured as JSON?

I'm exploring ways to add more elements to an object, but I'm uncertain about the process. My attempts to push data into the object have been unsuccessful. people = [{ name: 'robert', year: 1993 }]; //I aim to achieve this peopl ...

Unable to execute any actions on object in JavaScript

I currently have two functions in my code: getRawData() and getBTRawData(). The purpose of getBTRawData() function is to retrieve data from Bluetooth connected to a mobile device. On the other hand, getRawData() function takes the return value from getB ...

Steps to resolve the Angular observable error

I am trying to remove the currently logged-in user using a filter method, but I encountered an error: Type 'Subscription' is missing the following properties from type 'Observable[]>': _isScalar, source, operator, lift, and 6 more ...

Enrolling a new plugin into a software repository

I have 5 unique classes: ConfigManager ForestGenerator TreeCreator NodeModifier CustomPlugin My goal is to create an npm module using TypeScript that incorporates these classes. The main feature I want to implement is within the ConfigManager clas ...

Data retrieval from DynamoDB DocumentClient does not occur following a put operation

I am currently working on testing a lambda function using the serverless framework in conjunction with the sls offline command. The purpose of this lambda is to connect to my local DynamoDB, which has been initialized with a docker-compose image, and inser ...

Updating and Preserving Content in Angular

I've encountered an issue while working on a code that allows users to edit and save a paragraph on the screen. Currently, the editing functionality is working fine and the save() operation is successful. However, after saving, the edited paragraph do ...

Modifying the functionality of "use-input" in Vue.js

Currently, I am utilizing vue.js along with typescript to create an input field that allows users to either choose items from a drop-down menu or manually type in their own input. There are various scenarios where custom input might be allowed or where onl ...

Angular and Jest combo has encountered an issue resolving all parameters for the AppComponent. What could be causing this error?

I am currently working within a Typescript Monorepo and I wish to integrate an Angular 8 frontend along with Jest testing into the Monorepo. However, I am facing some challenges. The tools I am using are: Angular CLI: 8.3.5 My Approach I plan to use ...

Error in Typescript index: iterating over properties of a typed object

My scenario involves an interface that extends multiple other interfaces from an external library: interface LabeledProps extends TextProps, ComponentProps { id: string; count: number; ... } In a different section of the codebase, there is an ...