What is the best way to implement a switch case with multiple payload types as parameters?

I am faced with the following scenario:

  public async handle(
    handler: WorkflowHandlerOption,
    payload: <how_to_type_it?>,
  ): Promise<StepResponseInterface> {
    switch (handler) {
      case WorkflowHandlerOption.JOB_APPLICATION_ACTIVITY: {
        const {
          entity_id: jobApplicationEntityId,
          status: jobApplicationStatus,
        } = await this.jobApplicationActivityHandler.execute(payload);
...other staff
break;
}
case WorkflowHandlerOption.BILLING_FEE: {
const { entity_id: billingFeeId, status: billingFeeStatus } =
await this.billingFeeHandler.execute(payload);
...other staff
break;
}
case WorkflowHandlerOption.DOCUMENT_GENERATION: {
const { entity_id: documentId, status: documentStatus } =
await this.documentGenerationHandler.execute(payload);
...other staff
break;
}
case WorkflowHandlerOption.UPDATE_JOB: {
const { entity_id: jobId, status: jobStatus } =
await this.jobHandler.execute(payload);
...other staff
break;
}
case WorkflowHandlerOption.DOCUMENT_GENERATION_ACTIVITY: {
const {
entity_id: documentActivityId,
status: documentActivityStatus,
} = await this.documentGenerationActivityHandler.execute(payload);
...other staff
break;
}

default:
throw new BadRequestException('Handler not found');
}

//...other staff
}

I have multiple handlers that receive completely different payloads. How should I define the type for the payload parameter?

Considering there will be around 50 cases...

The only solution I have come up with is to use Class Validator to validate the payload for each case.

I prefer to stick to using TypeScript types without relying on third-party libraries

Answer №1

Create a custom data structure that consolidates tuple variations to encompass all potential combinations of arguments accepted by your function.

For example:

enum MyEnum { A, B, C }

type HandlerArgs =
  | [handler: MyEnum.A, payload: { a: string }]
  | [handler: MyEnum.B, payload: { b: number }]
  | [handler: MyEnum.C, payload: { c: boolean }]

The HandlerArgs type connects specific payloads with corresponding enum values.

You can now construct an implementation similar to this:

class Foo {
  async handle([handler, payload]: HandlerArgs) {
    switch(handler) {
      case MyEnum.A: {
        console.log(payload.a) // works as expected
        break
      }
      case MyEnum.B: {
        console.log(payload.b) // functions appropriately
        break
      }
      case MyEnum.C: {
        console.log(payload.c) // operates correctly
        break
      }
    }
  }
}

This showcases how the type of payload precisely conforms to the payload type based on the matched argument tuple.

See Playground


You could also abstract payload types from the parameters of functions where these payloads are utilized. This can be accomplished using the Parameters type to extract the function arguments and infer the payload types accordingly.

For instance:

enum MyEnum { A, B, C }

type HandlerArgs =
  | [handler: MyEnum.A, payload: Parameters<Foo['aHandler']['execute']>[0]]
  | [handler: MyEnum.B, payload: Parameters<Foo['bHandler']['execute']>[0]]
  | [handler: MyEnum.C, payload: Parameters<Foo['cHandler']['execute']>[0]]

... (remaining content remains unchanged)</answer1>
<exanswer1><div class="answer accepted" i="75676297" l="4.0" c="1678285645" m="1678296893" v="1" a="QWxleCBXYXluZQ==" ai="62076">
<p>Create a type that is a union of tuple types that represents the mapping of all the combinations of arguments your function accepts.</p>
<p>For example:</p>
<pre><code>enum MyEnum { A, B, C }

type HandlerArgs =
  | [handler: MyEnum.A, payload: { a: string }]
  | [handler: MyEnum.B, payload: { b: number }]
  | [handler: MyEnum.C, payload: { c: boolean }]

The HandlerArgs type binds specific payloads with specific enum values.

Now you can write an implementation like this:

class Foo {
  async handle([handler, payload]: HandlerArgs) {
    switch(handler) {
      case MyEnum.A: {
        console.log(payload.a) // fine
        break
      }
      case MyEnum.B: {
        console.log(payload.b) // fine
        break
      }
      case MyEnum.C: {
        console.log(payload.c) // fine
        break
      }
    }
  }
}

Here you can see that the type of payload is getting correctly narrowed to the payload type in the arguments tuple that was matched.

See Playground


You could even pull the payload types from the arguments of the functions you plan to pass those payloads to. You would do this by using the Parameters type to find the arguments of the functions and and derive that payload types from that.

For example:

enum MyEnum { A, B, C }

type HandlerArgs =
  | [handler: MyEnum.A, payload: Parameters<Foo['aHandler']['execute']>[0]]
  | [handler: MyEnum.B, payload: Parameters<Foo['bHandler']['execute']>[0]]
  | [handler: MyEnum.C, payload: Parameters<Foo['cHandler']['execute']>[0]]

class Foo {
  // mock some services this class uses
  declare aHandler: { execute: (payload: { a: string }) => void }
  declare bHandler: { execute: (payload: { b: number }) => void }
  declare cHandler: { execute: (payload: { c: boolean }) => void }

  async handle([handler, payload]: HandlerArgs) {
    switch(handler) {
      case MyEnum.A: {
        this.aHandler.execute(payload)
        break
      }
      case MyEnum.B: {
        this.bHandler.execute(payload)
        break
      }
      case MyEnum.C: {
        this.cHandler.execute(payload)
        break
      }
    }
  }
}

Here

Parameters<Foo['aHandler']['execute']>[0]
drills into the type of Foo.aHandler.execute and finds the parameters of that function, and then uses the first parameter as the payload type.

See 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

Retrieving a numerical value from a constantly changing string

The string is constantly changing. For example: Date15:Month8:Year1990 Is there a way to extract the number 15 without using substring, since the values are always different? I am looking to extract only the number after "Date" and before ":". ...

The issue with the tutorial is regarding the addHero function and determining the source of the new id

Whenever I need to introduce a new superhero character, I will utilize the add(string) function found in heroes/heroes.component.ts add(name: string): void { name = name.trim(); if (!name) { return; } this.heroService.addHero({ name } as H ...

Can you please explain the significance of the code "!!~this.roles.indexOf('*')" within the MEAN.io framework?

One particular line of code can be found in the startup file for the MEAN framework. if (!!~this.roles.indexOf('*')) { This specific line is located within the shouldRender function of the menus.client.service.js file, which resides in the publ ...

Utilizing JavaScript to bring JSON image data to the forefront on the front-end

In my quest to utilize JavaScript and read values from a JSON file, I aim to showcase the image keys on the front-end. To provide clarity, here's an excerpt from the JSON dataset: { "products": {"asin": "B000FJZQQY", "related": {"also_bought": ...

Display a sublist when a list item is clicked

I am receiving a JSON object in my view that looks like this: $scope.mockData = [ { "folder": "folder1", "reports": [{ "name": "report1" }, { "name": "report2" }, { "name": "report3" }] }, { "folder": "folder2", "reports": [{ "name": ...

Unusual actions observed when updating a record in MongoDB

I'm facing an issue with updating an entry in mongodb using lodash. The problem is that only one value in the array gets updated. I have sent the object below to my node.js server: { _id: 5593df7c087e59a00c04cda3, name: 'blueberry', uui ...

Error-throwing constructor unit test

In my code, I have implemented a constructor that takes in a configuration object. Within this constructor, I perform validations on the object. If the validation fails, I aim to throw an error that clearly describes the issue to the user. Now, I am wonde ...

Refresh React Components on the Fly (Solr)

I am relatively new to ReactJS In my React class, I have a function that is rendering multiple items: (Sample) var app = app || {}; app.Results = React.createClass({ componentDidMount: function () { }, handleUpdateEvent: function(id) ...

What is the best way to send the $_SESSION['var'] array to jquery and initiate an ajax request?

I'm dealing with an issue here. I need to retrieve all the items within the $_SESSION['cart'] array and pass it to jQuery so that it can be used in a php-ajax file. My question is, how can this be accomplished? This is what I have in mind: ...

how to adjust the width of a window in React components

When attempting to adjust a number based on the window width in React, I encountered an issue where the width is only being set according to the first IF statement. Could there be something wrong with my code? Take a look below: const hasWindow = typeof ...

Guide to making the root directory mount to a folder in Express

I have been attempting to mount a specific directory within my application to the root. The layout of my app is as follows: /app /server.js /views /statics /index.html /partials /public /javascript /css /images My goal i ...

Creating a nested list component using an array of objects

Seeking guidance for a coding task I recently completed. I was tasked with creating a multiple nested list from an array of objects. While I achieved the expected result, my code ended up being overly complicated and not very clean. I used a combination of ...

Utilizing JSON with Express version 4.11 and the bodyParser middleware

I've been trying to figure this out for hours, searching through Stackoverflow and the internet in general. I've attempted different solutions but still can't seem to get it right. As a newcomer to node.js, I managed to follow a tutorial to ...

Tips for establishing a universal onkeydown listener for all frames within a webpage?

Working with a complex legacy multi-frame webpage that needs to be compatible with IE-11 and responsive to hotkey events has brought up some challenges. It appears I can't simply declare a JavaScript method in the parent page. Rather, it seems that e ...

Enhancing systemjs-builder with DefinitelyTyped

Is there a dedicated TypeScript definition file for systemjs-builder available? https://github.com/systemjs/builder I encountered an issue where the systemjs.d.ts file does not seem to cover it, leading to a "cannot find module" error when trying to impor ...

Clearing the Redux state in my app upon exiting the page

I've come across similar inquiries, but none of them quite match my situation. When a user clicks on one of the buttons in my app, it triggers a get request to fetch data and then displays that data on the screen. However, the issue arises when I nav ...

Examining whether an ajax call was not initiated within an Angular application

Is there a way to verify that an ajax request has not been made using Angular's $httpBackend? I attempted to use verifyNoOutstandingRequest() but it doesn't seem to be triggering test failures in version 1.1.5. Here is more information about the ...

Having Trouble with Shopify's Asynchronous Ajax Add to Cart Feature?

I have developed a unique Shopify page design that allows users to add multiple items to their cart using Shopify's Ajax API. I've created a test version of the page for demonstration: Below is the current javascript code I am using to enable as ...

Passing an object in Vue.js during a redirect

i currently have two components named projectListComponent and projectSingleComponent. I want to pass an object from component 1 to component 2 during redirection. Here is my code: projectListComponent.vue <template> <div class="row justify ...

Node.js unleashes the power of Ajax

I have seen some people ask this question before, but I am having trouble understanding the responses :/ I am working with node.js and really want to utilize Ajax in my project. Here is a snippet of my code: var $ = require('jquery'); var http ...