How can we effectively handle model instantiation and creation in typescript?

I've been facing challenges when it comes to instantiating and initializing a model in TypeScript. In my project, I am using Nest where I receive an object from an API that needs to be validated by converting it to a DTO. This DTO should then be used to create an instance of the model.

The conventional approach would look something like this:

class MyModel {
   public constructor(public property: string) {
   }
}

class MyModelDto {
    public property: string;
}

// Instantiation function, taking input from an HTTP POST body:
public function createMyModel(bodyData: MyModelDto) {
      const myModel = new MyModel(bodyData.property);
}

The benefit of this method is that it ensures that a MyModel instance will never be created with incorrect properties, as they are all required and you cannot pass extra properties to the model. However, the drawback is having to include the full list of properties each time you instantiate your model. This is manageable with a few properties but becomes tedious when dealing with multiple (10+). Additionally, since the order matters, there is a risk of assigning data to the wrong property. As long as the types match, the compiler won't raise any errors.

An alternative approach could be:

class MyModel {
   public property: string;

   public constructor(data: MyModelInterface) {
      Object.assign(this, data);
   }
}

interface MyModelInterface {
  property: string;
}

class MyModelDto {
    public property: string;
}

// Instantiation function, taking input from an HTTP POST body:
public function createMyModel(bodyData: MyModelDto) {
      const myModel = new MyModel(bodyData);
}

This method offers a cleaner way of instantiation. Even though you need to specify the same list of properties twice (in the model and interface), it never exceeds 2. While this is preferable, the downside is that the compiler will only identify incorrect bodyData if you omit properties from the MyModelDto definition. It won't raise any issues if you add extra properties, and it won't exclude any data from your const myModel. This diminishes many benefits of TypeScript.

It would be ideal if Object.assign only accepted the properties specified in the MyModel class, or if the MyModel interface flagged any differences between the input and the interface, not just missing properties. Unfortunately, JavaScript does not function in this manner.

I have explored other options (like creating my own version of Object.assign), but none seem to provide a clean solution. I find it somewhat surprising that such a fundamental issue lacks a straightforward resolution (though perhaps I am simply being naive).

Answer №1

When dealing with the conversion of data between two different representations, my approach involves creating an interface using generics to define a mapper:

export interface DataMapper<A, B> {
    convertToTarget(source: A): B;

    convertToSource(target: B): A;
}

Within the implementation of the methods for converting to the target and source representations, I handle the mapping of information between the two classes:

class CustomMapper implements DataMapper<CustomModel, CustomModelDTO> {
    public convertToTarget(source: CustomModel) {
        let target = new CustomModelDTO(....);
        // map source info to target
        ....
        return target;
    }
    
    public convertToSource(target: CustomModelDTO) {
        let source = new CustomModel(....);
        // map target info to source
        ....
        return source;
    }
}

The conversion process varies depending on the scenario.

At times, it can be as simple as iterating over properties like this:

for (let property in source) {
  if (target[property]) { 
    target[property] = source[property] 
  }
}

In other cases, I utilize an object to facilitate the mapping of model properties to those of the target, for example:

class CustomMapper implements DataMapper<CustomModel, CustomModelDTO> {
    private readonly modelToDtoMap = {foo: 'property1', bar: 'prop2'};

    public convertToTarget(source: CustomModel) {
        let target = new CustomModelDTO(....);
        Object.keys(this.modelToDtoMap).forEach((key) => {
            target[this.modelToDtoMap[key]] = source[key];
        });

        return target;
    }

    ....
}

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

Implementing method overrides in TypeScript class objects inherited from JavaScript function-based classes

I am facing a challenge with overriding an object method defined in a JavaScript (ES5) function-based class: var JSClass = function() { this.start = function() { console.log('JSClass.start()'); } } When I call the start() method, it pri ...

The function call with Ajax failed due to an error: TypeError - this.X is not a function

I am encountering an issue when trying to invoke the successLogin() function from within an Ajax code block using Typescript in an Ionic v3 project. The error message "this.successLogin() is not a function" keeps popping up. Can anyone provide guidance on ...

Redis throwing an error - when handling an event

import * as redis from 'redis' import configuration from '../../configuration/settings' const customer = redis.createCustomer(configuration.redis.port, endpoint, configuration.redis.options); customer.on('active', () => { ...

The form control is missing a specified name attribute, causing an error with the value accessor

<input type="email" class="form-control passname" [(ngModel)]="emailID" name="Passenger Email ID" placeholder="email" required pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$"/> <div class="shake-tool ...

How can we determine the props' type specific to each component?

type ComponentCProps = { c: string; }; function ComponentC(props: ComponentCProps) { return <div>component C</div>; } type ComponentDProps = { d: string; }; function ComponentD(props: ComponentDProps) { return <div>component D& ...

Is there a way to merge two observables into one observable when returning them?

I'm struggling with getting a function to properly return. There's a condition where I want it to return an Observable, and another condition where I'd like it to return the combined results of two observables. Here is an example. getSearc ...

Update the image on a webpage by simply clicking on the current image

Hey there, I'm looking to implement a feature where users can choose an image by clicking on the current image itself. Here's my code snippet. The variable url holds the default image. My goal is to make the current image clickable so that when ...

How to convert DateTime to UTC in TypeScript/JavaScript while preserving the original date and time

Consider the following example: var testDate = new Date("2021-05-17T00:00:00"); // this represents local date and time I am looking to convert this given Date into UTC format without altering the original date and time value. Essentially, 2021-0 ...

Obtaining the host and port information from a mongoose Connection

Currently, I am utilizing mongoose v5.7.1 to connect to MongoDb in NodeJS and I need to retrieve the host and port of the Connection. However, TypeScript is throwing an error stating "Property 'host' does not exist on type 'Connection'. ...

Utilizing a personalized (branched) @types package

I have taken the @types/stripe package from the DefinitelyTyped repository on GitHub and created my own version in my personal GitHub repo / npm module. I understand that this custom type module may not work automatically. I attempted to integrate it by ...

GPT Open-Source Initiative - Data Ingestion Challenge

Seeking assistance with setting up the following Open Source project: https://github.com/mayooear/gpt4-pdf-chatbot-langchain Encountering an error when running the npm run ingest command: Error [Error: PineconeClient: Error calling upsert: Error: Pinecon ...

Is it possible to place Angular Material components using code?

Currently, I'm in the process of creating a responsive Angular application. Is there any way to adjust the height and position of the <mat-sidenav-content></mat-sidenav-content> component in Angular Material programmatically without relyi ...

Having difficulty setting custom icons for clustering in Google Maps MarkerClusterer due to the absence of position data

I am struggling to understand the documentation for this utility. It seems that to customize cluster icons, I need to convert the cluster icon to a marker using the MarkerClusterer's renderer property and then apply icon styles as if it were a normal ...

Guide to locating a particular node within an array of nested objects by utilizing the object

Dealing with an array of nested objects, the goal is to compare values with a flat array and update the property matchFound. If the parent's matchFound is true, then all its children should inherit this value. treeData = [{ field: 'make&a ...

I encountered an issue while generating a crypto address on the Waves blockchain using the @waves/waves-crypto library in TypeScript

Encountering an issue with finding "crypto-js" in "@waves/waves-crypto". Despite attempts to uninstall and reinstall the module via npm and importing it using "*wavesCrypto", the error persists within the module's index.d.ts file. I am attempting to ...

Adding a value to an array in TypeScript

When trying to add values to an array in my code, I encountered an error stating that "number" is not a valid type for the array. someArray: Array <{ m: number, d: Date}> = []; this.someArray.push(500,new Date(2020,1,15)); ...

Is there a way to verify the presence of multiple array indices in React with TypeScript?

const checkInstruction = (index) => { if(inputData.info[index].instruction){ return ( <Text ref={instructionContainerRef} dangerouslySetInnerHTML={{ __html: replaceTextLinks(inputData.info[index].instruction) ...

Incorporate Select2 functionality within the Angular2 application

I'm currently working on incorporating the Select2 plugin into my Angular2 application. Successfully, I have managed to set up select2 and transform my multiple select fields as expected. However, I am now facing a challenge in retrieving the selected ...

A guide on integrating third-party npm modules/packages into an Angular 8 application

As someone who is new to Angular and TypeScript, I am finding it challenging to add a 3rd party module or package to my Angular app. After spending hours searching online, I have yet to come across a simple guide for this process. The specific problem I a ...

Uncovering the Image Orientation in Angular: Is it Possible to Determine the Direction Post-view or Upon Retrieval from Database?

I am currently working on creating centered and cropped thumbnails for images retrieved from a database. I came across some helpful information on how to achieve this: The resource I found is written for JavaScript, but I am using Angular 7. I am facing d ...