Discover the accurate `keyof` for a nested map in TypeScript

Here is the code snippet I'm working on:

const functions={
    top1: {
        f1: () => 'string',
        f2: (b: boolean, n: number) => 1
    },
    top2: {
        f3: (b: boolean) => b
    }
}

I am looking to define an apply function as follows:

function apply (top: keyof typeof functions, functionName: string, inputs: any[]) {
    return functions[top][functionName](...inputs)
}

This will allow me to output different values using console.log:

console.log(apply('top1', 'f1', [])); // 'string'
console.log(apply('top1', 'f2', [true, 23])); // 1
console.log(apply('top2', 'f3', [false])); // false
apply('top2', 'f3', [1]); // should throw a TS error

However, when in strict mode (--strict), the following error occurs:

"Element implicitly has an 'any' type because type '...' has no index signature"

This issue arises since functionName is defined as a string, not as a keyof typeof functions[section]. How can I resolve this?

Answer №1

To ensure type safety and allow indexing, generic type parameters must be used to capture the concrete keys passed to the function.

Conditional types can also be utilized to extract parameter types and return types for enhanced type safety when handling arguments and return values.

const functions={
    top1: {
        f1: () => 'string',
        f2: (b: boolean, n: number) => 1
    },
    top2: {
        f3: (b: boolean) => b
    }
}

type WeakParameters<T> = T extends (...a: infer A) => any ? A : never;
type WeakReturnType<T> = T extends (...a: any) => infer R ? R : never; 
function apply<KOutter extends keyof (typeof functions),
    KInner extends keyof (typeof functions[KOutter])>(
        top: KOutter, functionName: KInner, inputs: WeakParameters<(typeof functions)[KOutter][KInner]>) : WeakReturnType<(typeof functions)[KOutter][KInner]>{
    var fn = functions[top][functionName]; // OK
    return (fn as unknown as (...a:any[])=> any)(...inputs)
}
console.log(apply('top1', 'f1', [])); // 'string'
console.log(apply('top1', 'f2', [true, 23])); // 1
console.log(apply('top2', 'f3', [false])); // false
apply('top2', 'f3', [1]); // show throw TS error

Note Despite being able to index into `functions` with `top` and `functionName`, a type assertion is still necessary as TS cannot infer that `fn` is a function. This limitation also prevents the use of built-in conditional types like `Parameters` and `ReturnType`, necessitating the creation of custom versions for extracting parameter and return types without requiring proof of being a function.

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

Navigating through React Native with TypeScript can be made easier by using the proper method to pass parameters to the NavigationDialog function

How can I effectively pass the parameters to the NavigationDialog function for flexible usage? I attempted to pass the parameters in my code, but it seems like there might be an issue with the isVisible parameter. import React, { useState } from 'rea ...

Optimizing Angular for search engines: step-by-step guide

Regarding Angular SEO, I have a question about setting meta tags in the constructors of .ts files. I have implemented the following code: //To set the page title this.titleServ.setTitle("PAGE TITLE") //To set the meta description this.meta.addTag ...

Customizing the placeholder text for each mat input within a formArray

I have a specific scenario in my mat-table where I need to display three rows with different placeholder text in each row's column. For example, test1, test2, and test3. What would be the most efficient way to achieve this? Code Example: <div form ...

Mapping properties between objects in Typescript: transferring data from one object to another

Here are two different types and an object: type TypeX = { x: number; y: number; z: number; }; type TypeY = { u: number; v: number; w: number; }; initialObject: { [key: string]: TypeX }; The goal is to transfer the properties from an object of ...

What is the best way to implement a dynamic mask using imask in a React

I have a question regarding the implementation of two masks based on the number of digits. While visually they work correctly, when I submit the form, the first mask is always selected. How can I resolve this issue? My solution involves using imask-react ...

Quick tip: Adding a close 'X' button to an ng-bootstrap popover

As a newcomer to angular 5, I have been working on adding an 'x' button in the top right corner of a popover. Once this 'x' is clicked, the popover should be closed. Is there a way to achieve this using ng-bootstrap popover? Below is my ...

Generate a fresh array by filtering objects based on their unique IDs using Angular/Typescript

Hey there, I am receiving responses from 2 different API calls. Initially, I make a call to the first API and get the following response: The first response retrieved from the initial API call is as follows: dataName = [ { "id": "1", ...

Typescript error: the argument passed as type a cannot be assigned to the parameter of type b

In my programming interface, I have defined shapes as follows: type Shape = | Triangle | Rectangle; interface Triangle {...} interface Rectangle {...} function operateFunc(func: (shape: Shape) => void) {...} function testFunction() { const rectFun ...

Evaluating observables in .pipe angular 8 jasmine

In my component, I have a subscription to triggerRuleExecutionService which is triggered by another component using next(). Within the pipe, I am using switchMap to make an HTTP service call and retrieve data from the database. this.ruleExecutionService ...

Tips for transforming or changing Partial<T> into T

I have a variable named Partial<T> in my coding project. returnPartial(): Partial<T> {} proceed(param T) {} //<-- the provided input parameter will always be of type T in this function and cannot be changed let object = this.returnPartial( ...

What is the best way to add a custom typeguard to an object in TypeScript?

Looking to implement a type guard as an object method? I have an array of objects with similar data structures, but crucial differences that need to be checked and guarded using TypeScript. interface RangeElement extends Element { value: number; } inter ...

What is the best way to store a gridster-item in the database when it is resized or changed using a static function

Following some resize and drag actions on my dashboard, I aim to store the updated size and position of my altered widget in my MongoDB database. Even though the gridster library offers the ability to respond to draggable and resizable events, these events ...

Experiencing difficulty creating query files for the apollo-graphql client

I'm currently attempting to learn from the Apollo GraphQL tutorial but I've hit a roadblock while trying to set up the Apollo Client. Upon executing npm run codegen, which resolves to apollo client:codegen --target typescript --watch, I encounter ...

Performing several HTTP requests in a for loop in Angular 8

Within the backend, there exists an endless list of cars. Each car is designated by a unique id and has a corresponding model name. I possess a compilation of car IDs, as illustrated below: const carIds = ['abc','xyz']; My objective ...

Exporting a constant as a default in TypeScript

We are currently developing a TypeScript library that will be published to our private NPM environment. The goal is for this library to be usable in TS, ES6, or ES5 projects. Let's call the npm package foo. The main file of the library serves as an e ...

Exploring through objects extensively and expanding all their values within Angular

I am in need of searching for a specific value within an object graph. Once this value is found, I want to set the 'expanded' property to true on that particular object, as well as on all containing objects up the object graph. For example, give ...

Is it possible to retrieve a value obtained through Request.Form?

Within my Frontend, I am passing an Object with a PersonId and a FormData object. const formData = new FormData(); for (let file of files){ formData.append(file.name, file,); } formData.append('currentId',this.UserId.toString()); const upl ...

Retrieve the data from a JSON file using Angular 4

I have a JSON data structure that looks like this: [{"id": "ARMpalmerillas07", "type": "GreenHouse","act_OpenVentanaCen": {"type": "float", "value": 0, "metadata": {"accuracy": {"type": "Float", "value": "07/02/2018 13:08 : 43 "}}}, "act_OpenVentanaLatNS" ...

Is it possible to have an optional final element in an array?

Is there a more graceful way to declare a Typescript array type where the last element is optional? const example = (arr: [string, string, any | undefined]) => console.log(arr) example(['foo', 'bar']) When I call the example(...) f ...

Guide to resolving the error "Type 'void' cannot be assigned to type 'Function' in VueJS"

I've created a Vue component that requires a function pointer to execute a delete action. <template> <q-card class="my-card" > <q-img :src="media.normal || media.original"> <div class="absolute ...