transformation of categorized unions in software development

Experimenting with routing-controllers and its built-in class-transformer feature, I tried creating an interface for executing a search query based on either a location id or location coordinate. My aim was to utilize a discriminated union as a body parameter, but encountered issues where it did not function as expected. (Refer to the latest console output to understand what I mean by 'not working')

For instance:

interface LocationCoordinates {
    type: 'coordinate'
    longitude: number
    latitude: number
}

interface LocationId {
    type: 'id'
    id: number
}

class LocationRadius {
    data: LocationCoordinates | LocationId
    searchRadiusInKm: number
}

// raw input for LocationCoordinates
const rawLocationCoordinates = {
    data: {
        longitude: 22,
        latitude: 33
    },
    searchRadiusInKm: 30
}


// raw input for LocationId
const rawLocationId = {
    data: {
        id: 1
    },
    searchRadiusInKm: 30
}
// transform both raw inputs
const realLocationCoordinates = plainToClass(LocationRadius, rawLocationCoordinates);
const realLocationId = plainToClass(LocationRadius, rawLocationId);


console.log({
    coordinateType: realLocationCoordinates.data.type, // expects 'coordinate' but receives 'undefined'
    idType: realLocationId.data.type // expects 'id' but receives 'undefined'
});

Is there a way to make this work?

Answer №1

To achieve this, some modifications are necessary:

  1. LocationId and LocationCoordinates need to be converted into classes
  2. Include a @Type decorator for the input property. This will enable class-transformer to handle deserialization based on a specific discriminator parameter
class LocationRadius {
 @Type(() => Object, {
     keepDiscriminatorProperty: true,
     discriminator: {
         property: "type",
         subTypes: [
             { value: LocationCoordinates, name: "coordinate" },
             { value: LocationId, name: "id" }
         ]
     }
 })
 data: LocationCoordinates | LocationId
 searchRadiusInKm: number
}
  1. Add a type property to your input to allow TypeScript to differentiate between the union types:
 // raw input for LocationCoordinates
const rawLocationCoordinates = {
   data: {
       type: "coordinate",
       longitude: 22,
       latitude: 33
   },
   searchRadiusInKm: 30
}

For a demonstration of the changes, check out this StackBlitz project I have prepared

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

Propagating numerical values through iterative iterations

I am currently facing an issue with passing values as props to a component using the forEach method in JavaScript. In addition to passing the existing values from an array, I also want to send another value that needs to be incremented by 1 for each iterat ...

Top Recommendations: Comparing Standalone Components and Modules in Angular Version 14

I'm in need of some clarification on the most effective practices when it comes to utilizing standalone components and modules within Angular 14. With the introduction of standalone components as a new concept in Angular, I am seeking factual guidance ...

What is the reason behind TypeScript enclosing a class within an IIFE (Immediately Invoked Function

Behold the mighty TypeScript class: class Saluter { public static what(): string { return "Greater"; } public target: string; constructor(target: string) { this.target = target; } public salute(): string { ...

The type '{}' does not contain a property named 'map'

Recently delving into TypeScript, and I'm curious about the process of typing an axios response when storing it in a state variable. I've initiated a basic API call from App.tsx, which returns the following object: {data: {…}, status: 200, s ...

Can you explain the meaning of the code snippet: ` <TFunction extends Function>(target: TFunction) => TFunction | void`?

As I delve into the contents of the lib.d.ts file, one particular section caught my attention: declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; The syntax in this snippet is a bit perplexing to m ...

The specified column `EventChart.åå` is not found within the existing database

I've been developing a dashboard application using Prisma, Next.js, and supabase. Recently, I encountered an issue when making a GET request. Prisma throws an error mentioning a column EventChart.åå, with a strange alphabet "åå" that I haven&apos ...

What are the best ways to enhance change detection efficiency in Angular?

One issue I am facing involves two components and a service. It appears that when moving from the view of a routed component to elements in different components like a matMenu and an input field, the routed component seems to refresh itself. This becomes p ...

Two-way conditional type mapping

Currently, I am working on mapping the various "data types" of an object to a corresponding "schema" type. If the property's data type is boolean, it should be mapped to the "BooleanComponents" type The code snippet below demonstrates how this can ...

Tips for testing the setTimeout function within the ngOnInit using Jasmine

Could someone please assist me with writing a test for an ngOnInit function that includes a setTimeout() call? I am new to jasmine test cases and unsure of the correct approach. Any guidance would be greatly appreciated. app.component.ts: ngOnInit(): void ...

What measures can I take to ensure TypeScript transpiles prototype methods effectively?

Having some issues defining methods on a TypeScript class and then accessing them. Even though the methods are defined, it's giving an error that they don't exist. TypeScript: export class ColumnHeader extends JSONObject { // ... i ...

The module ~/assets/images/flags/undefined.png could not be found in the directory

When I use the img tag with a dynamic address filled using require, it works well in the component. However, when I try to write a test for it, an error is thrown. console.error Error: Configuration error: Could not locate module ~/assets/ima ...

How can I achieve this using JavaScript?

I am attempting to create a TypeScript script that will produce the following JavaScript output. This script is intended for a NodeJS server that operates with controllers imported during initialization. (Desired JavaScript output) How can I achieve this? ...

I am eager to learn how to integrate the "fs" module from Node.js into an Electron project powered by Angular

As I venture into writing my first desktop app using Electron and Angular5, I have encountered a roadblock while working with the fs module. Despite importing fs correctly (without errors in Visual Studio Code and with code completion), I faced an issue wh ...

Creating a second optional generic parameter in TypeScript

I'm having trouble with TypeScript generics My issue is as follows: I have an interface called 'Column' and a function called makeColumn to create a new column. I want to only set the first generic (UserModel), where the accessor must be a ...

Issue occurred while trying to set the value from an API call response in the componentDidMount lifecycle method

There is a boolean variable disableButton: boolean; that needs to be set based on the response received from this API call: async getDocStatus(policy: string): Promise<boolean> { return await ApiService.getData(this.apiUrl + policy + this.myEndpo ...

Tips for updating the icon based on the active or inactive status in ag-grid within an angular application

HTML* <ng-template #actionButtons let-data="data"> <div class="cell-actions"> <a href="javascript:;" (click)="assign()"> <i nz-icon nzType="user-add" nzTheme= ...

Connect AngularFire to a specific object

I'm facing an issue with my Users.class where I need it to automatically map or bind after fetching data from Firebase. I've been trying to search for the right term but haven't found any information yet. import { Component, OnInit } from & ...

Is there a way to combine multiple array objects by comparing just one distinct element?

Is there a way to combine these two arrays into one? array1 = [ { image: 'image1', title: 'title1' }, { image: 'image2', title: 'title2' }, { image: 'image3', title: 'title3' }, ]; array2 = ...

Guide on showing error message according to personalized validation regulations in Angular 2?

Utilizing a template-driven strategy for constructing forms in Angular 2, I have successfully implemented custom validators that can be utilized within the template. However, I am facing an issue with displaying specific error messages associated with dis ...

What is the process for obtaining a Component's ElementRef in order to access the BoundingClientRect of that specific Component?

I have successfully created a tooltip using Angular 5.2.0 and ngrx. The tooltip, among other things, receives an ElementRef to the target Element when the state updates, allowing me to position it absolutely: let rect = state.tooltip.target.nativeElement. ...