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

What is the reason behind shadow dom concealing HTML elements when viewed in inspect mode?

https://i.stack.imgur.com/UZM7f.png Monday.com has implemented Shadow Dom to protect its source code. How can I work around this limitation? ...

developing a multimedia player using HTML5

I'm attempting to create a dynamic video "collage" that showcases videos with different aspect ratios in a flexible grid layout. Each row should have videos of the same height, while filling up the horizontal space within a container. With known widt ...

How can I pull the account creation date stored in MongoDB and display it using Handlebars?

Currently in my development, I am utilizing MongoDB, NodeJS, and Handlebars. My challenge is to convert the user.id into a timestamp and then display this timestamp on my HTML page. At present, I can display the user.id by using {{ user.id }} in my code, ...

Reset Angular Material autocomplete upon selection

The issue I'm encountering is as follows: when an option is chosen from the autocomplete input, it should not only add a chip to the Angular Material Chips component (which it currently does), but also clear the autocomplete input so that another opti ...

What is the process for importing Buffer into a Quasar app that is using Vite as the build tool

I'm having issues with integrating the eth-crypto module into my Quasar app that utilizes Vite. The errors I'm encountering are related to the absence of the Buffer object, which is expected since it's typically found in the front end. Is ...

Leverage Prisma's auto-generated types as the input type for functions

Exploring the capabilities of Prisma ORM has led me to experiment with creating models and generating the PrismaClient. Initially, I thought it would be possible to utilize the generated types for variables and response types, but that doesn't seem to ...

Receive the most recent information from Angular's service method

I offer a unique service. handler.ts import { Observable,of,Subject } from 'rxjs'; import { PlayerService } from "./../../core/services/player.service"; import { Injectable } from "@angular/core"; import { DeezerService } from "../services/deez ...

Is it feasible to convert a Google Drive spreadsheet into JSON format without needing the consent screen?

I'm working on incorporating a JSON feed directly from a private spreadsheet (accessible only via link) onto my website. In order to do this, I must create a new auth token using OAuth 2.0, which is not an issue. However, the Google Sheets API v4 mand ...

Display a modal when a user is not authorized in vue-router

After stumbling upon this post on Medium, I decided to implement its concepts into my project. My goal was to verify a user's authorization to access a particular route, and if unauthorized, display a modal pop-up. In order to achieve this, I made s ...

Ways to create two distinct "on click" actions for a single image to execute two distinct tasks

Clicking on an image will open a slider showing all images before filtering. Once filtered, only the selected images will be displayed in the slider. <div class="gallery row" id="gallery" style="margin:0;"> <!-- Grid column --> <div ...

Troubleshooting the issue of CSS animations activating twice and causing a flickering effect specifically in the Firefox browser

I am facing an issue with CSS animations in Firefox. When I try to slide in some radio buttons upon clicking a button, the animation seems to be firing twice in Firefox while it works fine in Chrome. I have attempted several solutions but haven't been ...

Retrieving asynchronous data from a module and transmitting it via a route's endpoint

I'm currently in the process of constructing an API endpoint using Node. The goal is to retrieve data from an external API and then transfer this information through the created endpoint. Ultimately, this endpoint will be utilized on the client side. ...

Troubleshooting: JavaScript code not functioning properly with variable input instead of fixed value

I have encountered an issue with a JS function that I'm using. The function is shown below: // A simple array where we keep track of things that are filed. filed = []; function fileIt(thing) { // Dynamically call the file method of whatever ' ...

Uh oh! There seems to be an issue with the ClerkJS frontendAPI option. Visit the homepage at https://dashboard.clerk.dev to retrieve your unique Frontend API value

Despite inputting the correct Clerk API keys, I'm encountering issues with the functionality of the ClerkJS API. I anticipate that the application should enable me to utilize the ClerkJS API for user authentication without any problems. ...

Show the React component upon clicking the Add button

I recently developed a React component that reveals a new section whenever the user clicks the "New Section" button. However, I have encountered an issue - I would like the component to display multiple sections based on how many times the button is clic ...

Unable to retrieve the /socket.io/socket.io.js file in the client-side application

My server side application is currently hosted on heroku: The code snippet that is relevant to this issue is as follows: const express = require('express'), app = express(), server = require('http').createServer(app), ...

Modifying Div Size with Jquery

I am working on populating the div container with square boxes, but I'm having trouble adjusting the size of my #gridSquare div. Despite trying to change its height and width using jQuery, it doesn't seem to work as expected. $('#gridSquare ...

Tips on adjusting a JavaScript animation function

I'm currently working on adjusting the animation function within the "popup" class that controls the gallery section of a website. When the page loads, an image and background start expanding from zero scale in the center to 100 VIEWPORT HEIGHT (VH) a ...

Retrieve all instances of a key-value pair within an object that share the same key

Here is some JSON data: [{"name":"David","text":"Hi"},{"name":"Test_user","text":"test"},{"name":"David","text":"another text"}] I am l ...

Enhance the API response for Angular service purposes

I am currently working on modifying the response returned by an API request. At the moment, I receive the response as: [ { name: "Afghanistan" }, { name: "Åland Islands" } ] My goal is to adjust it to: [ { name: "A ...