Transform a dynamic set of distinct types into a different set of distinct types through mapping

If we consider the type union below:

type Entity = Circle | Square;

interface Circle {
    type: "circle";
    radius: number;
}

interface Square {
    type: "square";
    sideLength: number;
}

Is there a way to dynamically convert this discriminated type union into another with different discriminators? For example:

type EntityMessage =
  | {
      contentType: "application/vnd.entity.circle";
      content: {
        radius: number;
      };
    }
  | {
      contentType: "application/vnd.entity.square";
      content: {
        sideLength: number;
      };
    };

Answer №1

To accomplish this, you can utilize various TypeScript features such as generics, keyof type operator, indexed access types, conditional types, mapped types, template literal types and utility types.

Start by creating a discriminated union map:

type DiscriminatedUnionMap<Union extends object, Discriminant extends keyof Union> = {
  [
    DiscriminantType in Union[Discriminant] extends keyof any
      ? Union[Discriminant]
      : never
  ]: Omit<
    Extract<Union, { [_ in Discriminant]: DiscriminantType }>,
    Discriminant
  >;
};

/*
 * Equivalent to:
 *
 * type ShapeMap = {
 *   triangle: {
 *     base: number;
 *     height: number;
 *   };
 *   rectangle: {
 *     width: number;
 *     height: number;
 *   };
 * };
 */
type ShapeMap = DiscriminatedUnionMap<Shape, "type">;

Next, transform the map and extract its values as a type union:

/*
 * Equivalent to:
 *
 * type ShapeMessage =
 *   | {
 *       contentType: "application/vnd.shapes.triangle";
 *       content: {
 *         base: number;
 *         height: number;
 *       };
 *     }
 *   | {
 *       contentType: "application/vnd.shapes.rectangle";
 *       content: {
 *         width: number;
 *         height: number;
 *       };
 *     };
 */
type ShapeMessage = {
  [P in keyof ShapeMap]: {
    contentType: `application/vnd.shapes.${P}`;
    content: ShapeMap[P];
  };
}[keyof ShapeMap];

Here's an example of how you can use it:

function processShapeMessage(message: ShapeMessage) {
  switch (message.contentType) {
    case "application/vnd.shapes.triangle": {
      const { base, height } = message.content;
      console.log("Triangle base and height are:", base, "and", height, "respectively");
      break;
    }
    case "application/vnd.shapes.rectangle": {
      const { width, height } = message.content;
      console.log("Rectangle width and height are:", width, "and", height, "respectively");
      break;
    }
  }
}

Please note: The DiscriminatedUnionMap is reusable for any discriminated unions with a non-nested discriminant field.

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

How can I enhance the sharpness of shadows on MeshLambertMaterial in Three.js?

I am currently working on a project involving the creation of 3D images of the moon at different phases, incorporating libration effects. Here is a snapshot of my first quarter moon image: https://i.sstatic.net/MTb4f.png While the image looks good, I am ...

TSLint throws an error, expecting either an assignment or function call

While running tslint on my angular project, I encountered an error that I am having trouble understanding. The error message is: expected an assignment or function call getInfoPrinting() { this.imprimirService.getInfoPrinting().subscribe( response => ...

Is it possible to transform a webpack bundled javascript file into typescript source code?

Is it possible to decompile a webpack-bundled JavaScript file into TypeScript source code? I have a bundle.js file that was bundled using webpack, but the original source code files were accidentally deleted. I am hoping to reverse engineer the bundle.js ...

Issue encountered while parsing source map in Typescript CRA with MSW: failed

After creating a Create React App application with the TypeScript template and installing MSW using npm, I followed the installation guide to create the necessary files. While everything works perfectly for jest, I encountered a series of warnings when run ...

Display an image in an Angular application using a secure URL

I am trying to return an image using a blob request in Angular and display it in the HTML. I have implemented the following code: <img [src]="ImageUrl"/> This is the TypeScript code I am using: private src$ = new BehaviorSubject(this.Url); data ...

How can I restrict the highest possible date selection in reactjs?

I am working on a project that requires users to select dates using datetime-local, but I want to limit the selection to only three months ahead of the current date. Unfortunately, my current implementation is not working as expected. Any assistance woul ...

The parameter type 'string[]' cannot be assigned to the parameter type '("a" | "b" | "c")[]' in TypeScript

/** * Function to pick specific keys from an object. */ function pick<T, K extends keyof T> (obj: T, keys: K[]): Pick<T, K> { return keys.reduce((result, key) => { result[key] = obj[key]; return result; }, {} as any); } const ...

Sending an ID from an array within a *ngFor loop to a different component in Angular: a step-by-step guide

I have a collection of items that I loop through using ngFor. My goal is to pass all its attributes to another component. I attempted to accomplish this with the following code: *ngFor='let item of postList [routerLink]="['/detailed-post&ap ...

Struggling to retrieve an Object from an HTMLSelectElement within an Angular project

Can you help me with this code snippet: <select #categorySelect (change)="goToCategory.emit(categorySelect.value)" style="..."> <option selected hidden>All</option> <option [ngValue]="categ ...

Angular2: PrimeNG - Error Retrieving Data (404 Not Found)

I'm facing an issue with using Dialog from the PrimeNG Module. The error message I keep getting is: Unhandled Promise rejection: (SystemJS) Error: XHR error (404 Not Found) loading http://localhost:4200/node_modules/primeng/primeng.js I followed the ...

Can you explain the significance of using curly braces in an import statement?

The TypeScript handbook has a section on Shorthand Ambient Modules, where an import statement is shown as: import x, {y} from "hot-new-module"; It doesn't explain why y is in curly braces in the above statement. If both x and y were inside the brace ...

Here are some steps to turn off browser autocomplete for a p-inputMask field

I need help in disabling the browser autocomplete on a field that uses p-inputMask. I have tried using autocomplete="nope" on other fields and it works perfectly, but for this specific case, it doesn't seem to work. Can anyone point out what I might b ...

Svelte: The selection issue that's not updating [CSS glitch]

I am utilizing a Svelte Selection component to fetch and display data in a dropdown. Users have the ability to select different properties, which is functioning correctly. However, I am encountering an issue when attempting to preselect certain options des ...

Develop an "Import Interface" using TypeScript

I have a large project with many files and I believe using an import object would be beneficial. For instance, consider having menu.ts at the top level that every program will refer to: import router from "./router/index"; import controllers from ...

Implementing a method to display HTML tags within text in a React application

I have been working on a text highlighting feature, and I have successfully detected the selection. Currently, I have something like Te<span>x</span>t within a string, represented as an array: ["Te", "<span>", "x ...

Running the `npm start` command in Angular tends to be quite time-consuming

When I use Visual Studio Code to run Angular projects, my laptop seems to take a longer time when running the server through npm start compared to others. Could this delay be related to my PC specifications, or is there something I can do to improve it? ...

Angular2 Navigation: Redirecting to a dynamically constructed route

To start, I need to automatically redirect to today's date as the default. Below is the routing configuration I currently have set up: import { CALENDAR_ROUTE } from './_methods/utils'; export const appRoutes: Routes = [ { path: Cal ...

Leveraging LESS in an Angular2 component

Currently, I am attempting to integrate LESS with angular2. To do so, I have imported the less.js file in my index.html and I am using the following command within the component: less.modifyVars({ '@currentTheme-bar': '@quot ...

BrowserRouter - The type '{ children: Element; }' is not compatible with the type 'IntrinsicAttributes', as they do not share any properties in common

After upgrading to React version 18, I encountered a type error with the BrowserRouter component. Despite trying various approaches, I am unable to pinpoint the root of the problem. Here is the error that pops up during debugging: Overload 1 of 2, &a ...

The parameter in the Typescript function is not compatible with the generic type

What causes func1 to behave as expected while func2 results in an error? type AnyObj = Record<string, any>; type Data = { a: number; b: string }; type DataFunction = (arg: AnyObj) => any; const func1: DataFunction = () => {}; const arg1: Data ...