Tips for bypassing switch statements in typescript while handling discriminators?

Is there a way to refactor the code below to eliminate the switch case while maintaining type safety? I've encountered this issue several times and am looking for a pattern that utilizes a map or another approach instead of a switch case.

function translate(when: When): SomeCommonResultType {
  switch (when.object) {
    case "timespan":
      return translateTimeSpan(when);
    case "time":
      return translateTime(when);
    case "datespan":
      return translateDateSpan(when);
    case "date":
      return translateDate(when);
  }
}

type When = Timespan | Time | DateSpan | Date;

export interface Timespan {
  readonly object: "timespan";
  startTime: number;
  endTime: number;
  startTimezone?: string;
  endTimezone?: string;
}

function translateTimeSpan(when: Timespan): SomeCommonResultType {
  ...
}

export interface Time {
  readonly object: "time";
  time: number;
  timezone: string;
}

function translateTime(when: Time): SomeCommonResultType {
  ...
}

export interface DateSpan {
  readonly object: "datespan";
  startDate: string;
  endDate: string;
}

function translateDateSpan(when: DateSpan): SomeCommonResultType {
  ...
}

export interface Date {
  readonly object: "date";
  date: string;
}

function translateDate(when: Date): SomeCommonResultType {
  ...
}

I typically avoid using 'any' like this:

const TranslateWhenMap = new Map<string, any>([
  ["timespan", translateTimeSpan],
  ["date", translateDate],
  ["datespan", translateDateSpan],
  ["time", translateTime],
]);

function translate(when: When): SomeCommonResultType {
  return TranslateWhenMap.get(when.object)(when);
}

Answer №1

When dealing with TypeScript, it's important to understand that the compiler can perform type checking on a block of code only once. This means it cannot analyze situations where a union type parameter, such as when, needs to be applied to a certain function, like

TranslateWhenMap.get(when.object)(when)
. Even if the keys and values in a Map were strongly typed (which they aren't due to limitations discussed here), the compiler cannot infer the appropriate type for when in each case separately. This behavior doesn't align with the expected logic, leading to confusion, as highlighted in microsoft/TypeScript#30581.

To ensure type checking across multiple scenarios within a code block, you may need to refactor your code and make use of generics. By restructuring your translate() function and utilizing matching generic types for the function calls, the compiler can better understand the intended operations. While the process may seem complex, resources like microsoft/TypeScript#47109 provide guidance on adapting code for such scenarios, although not all aspects may be directly applicable to your situation.

One approach to address this issue involves defining utility types like WhenObject to handle the union of object property types from When, followed by TranslateArg<K> to pinpoint the correct type based on the object property. By replacing the conventional Map with a strongly-typed translateWhenMap object and utilizing a generic function like translate(), specific type errors can be caught during compilation.

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

A guide on showcasing nested arrays data in an Angular application

info = [ { list: [ { title: 'apple'} ] }, { list: [ { title: 'banana'} ] } ] My goal here is to extract the list items. Here is how they are structured. desired r ...

Error detected in Deno project's tsconfig.json file, spreading into other project files - yet code executes without issues?

I am working on a Deno project and need to utilize the ES2019 flatMap() method on an array. To do this, I have created a tsconfig.json file with the following configuration: { "compilerOptions": { "target": "es5", ...

Discover the power of TypeScript's dynamic type inference in functions

Below is a function that selects a random item from an array: const randomFromArray = (array: unknown[]) => { return array[randomNumberFromRange(0, array.length - 1)]; }; My query pertains to dynamically typing this input instead of resorting to u ...

Changing namespaces or module containers that share the same name can result in a clash

Trying to reorganize some code that was mistakenly namespaced and move it to its own namespace without affecting the current functionality during the process. The original code is structured as follows: // (org) module General.Admin.Dialogs { export cl ...

Tips for determining if a key is present in local storage:

I need to set a key value, but only if it doesn't already exist. In my component1.ts file, I am assigning the key and value in the constructor. However, I want to include a condition that this action should only be taken if the key is not already pre ...

Using Material UI Slider along with Typescript for handling onChange event with either a single number or an

Just diving into Typescript and encountered an issue with a Material UI Slider. I'm trying to update my age state variable, but running into a Typescript error due to the typing of age being number and onChange value being number | number[]. How can I ...

Accessing and sending only the body part of an HTTP response in Angular 7 test cases: A comprehensive guide

Currently, I am working on creating unit test cases in Angular 7 for a Component that utilizes an asynchronous service. This is the content of my component file: submitLoginForm() { if (this.loginForm.valid) { // send a http request to save t ...

Achieve top-notch performance with an integrated iFrame feature in Angular

I am trying to find a method to determine if my iframe is causing a bottleneck and switch to a different source if necessary. Is it possible to achieve this using the Performance API? This is what I currently have in my (Angular) Frontend: <app-player ...

Angular 8 bug: Requiring 2-3 arguments, received only 1

Currently deepening my knowledge in Angular and I encountered a situation within one of my services agree(id: string) { const headers = new HttpHeaders('Content-Type: application/json'); return this.HttpClient.put(`${this.apiUrl}/agree/` ...

Utilizing Angular 7, Ngrx, and Rxjs 6 to efficiently share state data among lazily loaded modules

Currently, I am working with Angular 7 alongside Ngrx and Rxjs 6. In my project, I have two lazy loaded modules named A and B, each with its own selectors and reducers. The challenge I am facing is accessing the data stored in module B's state from m ...

Ways to compel string type or disregard type?

When using the following code snippet: <Image src={user?.profilePictureUrl} alt={user?.name} /> An error is encountered: Type 'string | null | undefined' is not assignable to type 'string | StaticImport'. Type 'undefined ...

What is the correct way to declare a class as global in TypeScript?

To prevent duplication of the class interface in the global scope, I aim to find a solution that avoids redundancy. The following code snippet is not functioning as intended: lib.ts export {} declare global { var A: TA } type TA = typeof A class A { ...

"Choose one specific type in Typescript, there are no in-b

Need help returning an object as a fetch response with either the property "data" or "mes": { data: Data } | { mes: ErrMessage } Having trouble with TypeScript complaining about this object, let's call it props: if (prop.mes) return // Property &a ...

Allowing HTML attributes in reusable components with Vue TSX: A guide on informing Typescript

Imagine I have a custom input component: import { defineComponent } from "@vue/runtime-core" export default defineComponent({ inheritAttrs: false, setup(props, { attrs }) { return () => ( <div> ...

Clear all events from an HTML element and its descendants with TypeScript

Each time the page loads, I have HTML from an API that is constantly changing. Is there a way to strip away all events attached to it? The original HTML looks like this: <div id="content"> <h2 onclick="alert('hi');">Test 1< ...

What are the potential downsides of using ID to access HTML elements in React TypeScript?

During my previous job, I was advised against accessing HTML elements directly in React TypeScript using methods like getElementById. Currently, as I work on implementing Chart.js, I initially used a useRef hook for setting up the chart. However, it appear ...

Using ngModel with a dynamic variable

Having a issue with using ngModel to pass a value to bump object property retrieved from the bumpDetail.name array. I've included my code snippet below. Can someone please review it and point out where I've made a mistake? Thank you. <p * ...

Running TypeScript Jest tests in Eclipse 2020-12: A step-by-step guide

Having a TypeScript project with a test suite that runs smoothly using npm test in the project directory from the console. Is there a feature within Eclipse that can facilitate running these tests directly within the IDE, similar to how Java tests are ex ...

A long error occurred while using the payloadaction feature of the Redux Toolkit

import { createSlice, PayloadAction, createAsyncThunk } from "@reduxjs/toolkit" import axios, { AxiosError} from "axios" type user = { id: number, token: string } export type error = { error: string } interface authState { user: user | ...

Ways to capture targeted requests

Utilizing NestJS and Angular 2, I have noticed that both frameworks have a similar approach when it comes to working with Interceptors. I am currently looking for the best practice to identify specific requests in order to perform additional tasks. When d ...