Function argument-driven dynamic typing

I have a function that accepts multiple boolean flags, each requiring different arguments to be passed.

Here is a simplified version:


enum FLAGS {
  A = 'a',
  B = 'b',
  C = 'c'
}

interface ARelatedArgs {
  aBool: boolean
  aString: string
}

interface BRelatedArgs {
  (bEvt: any): void
  bBool: boolean
}

interface CRelatedArgs {
  (cEvt: any): string
  cString: string
}


interface MyFunctionArgs {
  flags: Partial<Record<FLAGS, boolean>>
  // other properties based on flags
}

function myFunction(args: MyFunctionArgs) {
  // do something 
}

Now I want to have type inference based on these calling patterns:


// First call to my function
myFunction({
  flags: { [FLAGS.A]: true }
  // all ARelatedArgs
})


// Second call to my function
myFunction({
  flags: { [FLAGS.A]: true, [FLAGS.B]: true }
  // all ARelatedArgs
  // + all BRelatedArgs
})

// Last call to my function
myFunction({
  flags: { [FLAGS.A]: true, [FLAGS.B]: true, [FLAGS.C]: true }
  // all ARelatedArgs
  // + all BRelatedArgs
  // + all CRelatedArgs 
})

I am looking for TypeScript to check and infer argument types based on the passed flags. I'm interested in having IntelliSense assist with this when passing multiple flags rather than just one. Can TypeScript provide this functionality?

I understand that I am requesting runtime checking from TypeScript. How can I achieve this?

I envision MyFunctionArgs to be structured like this:

interface MyFunctionArgs {
  flags: Partial<Record<FLAGS, boolean>>
  ...(FLAGS.A in flags && ARelatedArgs),
  ...(FLAGS.B in flags && BRelatedArgs),
  ...(FLAGS.C in flags && CRelatedArgs),
}

Answer №1

A key-value map is required to connect the flags enum with corresponding types:

// type TypeMap = {
//     a: ARelatedArgs;
//     b: BRelatedArgs;
//     c: CRelatedArgs;
// }
type TypeMap = Prettify<
  {
    [K in FLAGS.A]: ARelatedArgs;
  } & { [K in FLAGS.B]: BRelatedArgs } & { [K in FLAGS.C]: CRelatedArgs }
>;

Prettify is a useful tool for improving readability of end types by simplifying syntax.

Next, a utility is designed to utilize mapped types and key remapping to filter out unnecessary arguments based on passed flags:

type FlagsArgs<T extends MyFunctionArgs['flags']> = {
  [K in keyof T & keyof TypeMap as T[K] extends true ? K : never]: TypeMap[K];
};

// type Result = {
//     a: ARelatedArgs;
//     b: BRelatedArgs;
// }
type Result = FlagsArgs<{ [FLAGS.A]: true; [FLAGS.B]: true }>;

To achieve the desired outcome, specific types are necessary: ValueOf - retrieves values under each key of an object as a union:

type ValueOf<T> = T[keyof T];

Testing phase:

type FlagsArgs<T extends MyFunctionArgs['flags']> = ValueOf<{
  [K in keyof T & keyof TypeMap as T[K] extends true ? K : never]: TypeMap[K];
}>;

// type Result = ARelatedArgs | BRelatedArgs
type Result = FlagsArgs<{ [FLAGS.A]: true; [FLAGS.B]: true }>;

To convert the union into an intersection, UnionToIntersection can be utilized as explained here:

type FlagsArgs<T extends MyFunctionArgs['flags']> = UnionToIntersection<
  ValueOf<{
    [K in keyof T & keyof TypeMap as T[K] extends true ? K : never]: TypeMap[K];
  }>
>;

// type Result = ARelatedArgs & BRelatedArgs
type Result = FlagsArgs<{ [FLAGS.A]: true; [FLAGS.B]: true }>;

Wrapping FlagsArgs with Prettify further refines the result type:

type FlagsArgs<T extends MyFunctionArgs['flags']> = Prettify<
  UnionToIntersection<
    ValueOf<{
      [K in keyof T & keyof TypeMap as T[K] extends true
        ? K
        : never]: TypeMap[K];
    }>
  >
>;

// type Result = {
//     aBool: boolean;
//     aString: string;
//     bBool: boolean;
// }
type Result = FlagsArgs<{ [FLAGS.A]: true; [FLAGS.B]: true }>;

Looking good!

Application:

function myFunction<T extends MyFunctionArgs>(args: T & FlagsArgs<T['flags']>) {
  // perform actions
}

Final test cases:

// 1st Call to my function
myFunction({
  flags: { [FLAGS.A]: true },
  aBool: true,
  aString: 'str',
});

// 2nd Call to my function
myFunction({
  flags: { [FLAGS.A]: true, [FLAGS.B]: true },
  aBool: true,
  aString: '',
  bBool: false,
});

// last Call to my function
myFunction({
  flags: { [FLAGS.A]: true, [FLAGS.B]: true, [FLAGS.C]: true },
  aBool: true,
  aString: '',
  bBool: false,
  cString: '',
});

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

Extensible generic type/interface in Typescript

Looking to create a versatile base interface or type that can adapt its properties based on the generic object it receives. It might look something like this: interface BaseObject<Extension extends object = {}>{ a: string; b: string; {...Ext ...

Can we expect Karma to receive updates for upcoming versions of Angular and Jasmine?

We recently attempted to upgrade our company's Angular module, which required updating dependencies as well. Upon upgrading to the latest versions, we encountered an issue with the Jasmine-karma-HTML-Reporter due to its reliance on Jasmine-core 4.x.x ...

What is the best way to retrieve response data from an http request in Angular?

I am looking to retrieve a response from a GET HTTP request, and my server is written in JavaScript. The specific part where I send a response is as follows: app.get('/getReport',function(req,res) { try { const data=fs.readFileSync('./ ...

Retrieving a variable value set within a jQuery function from within an Angular 2 component

In the current project, I am facing a situation where I need to work around and initialize jQuery datetimepicker inside an Angular 2 application (with plans to refactor it later). However, when I assign a datetime value to a variable, I encounter a proble ...

Issue found in React Js test - TypeError: source.on does not exist as a function

I'm encountering an issue with my post request using multipart/form-data. Everything runs smoothly, except for the tests which are failing. When running the tests, I encounter an error message: TypeError: source.on is not a function. This is the code ...

Error: JSON unexpected token ' at position 2 - Solution for fixing this issue

Encountering a recurring JSON error where the user input from a textbox is being passed in JSON for assigning class level permissions in a parse server. var cc = "role:" + user; var jsonParam = "{ 'classLevelPermissions' : { ...

Firestore emulator outperforms Firestore in terms of performance

My application is capable of handling a substantial volume of write, read, and update operations (potentially exceeding 10000) under specific conditions. During the development of the app on a local environment, these operations usually complete within a ...

Yes, it's not able to retrieve the value from headlessui combobox

I have encountered an issue while using the Headlessui combobox component in conjunction with Yup. Despite successfully storing the selected value in the selectedMemory state variable, Yup consistently generates a required error message. I seem to be overl ...

Enhance the performance of your React/NextJS app by controlling the rendering of a component and focusing on updating

I'm facing an issue with my NextJS application that showcases a list of audio tracks using an <AudioTrackEntry> component. This component receives props like the Track Title and a Link to the audio file from an external data source. Within the ...

Filtering an array with radio buttons in Angular

I am working with a JSON list that contains various audio files categorized by genre, along with their corresponding URLs. export const musicList = [ { 'id': 0, 'catName': 'upload', 'audios': [ { ...

The parameter of type "Construct" cannot be assigned the argument of type "undefined"

I attempted to use code example from the AWS CDK documentation, but it did not function as I had anticipated. Using CDK version 2.62.2 with Typescript. In various parts of the code, a declaration error occurs stating that The argument of type "undefi ...

What is the process for configuring PhpStorm to sync with TypeScript tsconfig.json in .vue files?

Vue.js (webpack + vueify) with TypeScript is my current setup. The ts configuration appears to be functioning, but only in .ts files. For instance, in tsconfig.json: "compilerOptions": { "strictNullChecks": false, So strictNullChecks works as expect ...

Despite the presence of installed definition files, Typescript is unable to detect the module

After following the detailed instructions on how to install lodash into my Ionic 2 project from this link: , I still encountered an error during compilation: Error TS2307: Unable to locate module 'lodash'. The way I am importing the module in ...

Creating string enums in NextJS with TypeScript

I am working on my nextjs application and I have a component with a prop that is a string. I decided to create an enum, so I attempted the following: enum Label { dermatology = 'Dermatologi', psychology = 'Psykologi', rheumatology = ...

Ways to simulate objects in jest

I'm facing an issue while trying to mock a function of an object using Jest and Typescript. Below is a concise version of my code: // myModule.ts export const Foo = { doSomething: () => { // ... does something console.log('original ...

"Troubleshooting the routerLink binding issue in Angular 2 beta14 with Types

In my endeavor to construct a wizard using Angular2's router, I stumbled upon the suggestion of having a main bootstrap file by Angular2, which then bootstraps all the app components. However, as I am unable to create a Single Page Application (SPA), ...

Sending data to a parent component from a popup window in Angular Material using a button click while the window is still open

How can I retrieve data from an Angular Material Dialog Box and send it to the Parent component? I am able to access data after the dialog box is closed. However, I am wondering if there is a way to retrieve data while the dialog box is still open, especi ...

Issue with null parameter in Pulumi's serviceAccountKey.privateKeyData callback

My code in index.ts is as follows: import {Key, ServiceAccount} from "@pulumi/google-native/iam/v1"; const serviceAccountKey = new Key('service-account-key-' + new Date().toISOString(), { serviceAccountId: serviceAccount. ...

Ways to merge values across multiple arrays

There is a method to retrieve all unique properties from an array, demonstrated by the following code: var people = [{ "name": "John", "age": 30 }, { "name": "Anna", "job": true }, { "name": "Peter", "age": 35 }]; var result = []; people. ...

Express not functioning properly with custom error handler

I'm facing an issue while trying to implement a custom error handler for my Express routes. Despite following the instructions in the documentation which recommend placing the custom handler at the end of the use chain, it seems that the default error ...