Creating intricate structures using TypeScript recursively

When working with Angular and TypeScript, we have the power of generics and Compile-goodness to ensure type-safety. However, when using services like HTTP-Service, we only receive parsed JSON instead of specific objects. Below are some generic methods that demonstrate this:

public get<T>(relativeUrl: string): Promise<T> {
    const completeUrlPromise = this.createCompleteUrl(relativeUrl);
    const requestOptions = this.createRequestOptions(ContentType.ApplicationJson, true);
    return completeUrlPromise.then(completeUrl => {
      return this.processResponse<T>(this.http.get(completeUrl, requestOptions));
    });
  }

  private processResponse<T>(response: Observable<Response>): Promise<T> {
    const mappedResult = response.map(this.extractData);

    const result = mappedResult.toPromise();
    return result;
  }

  private extractData(res: Response): any {
    let body;
    if (!Array.isArray(res)) {
      if (res.text()) {
        body = res.json();
      }
    } else {
      body = res;
    }

    if (!JsObjUtilities.isNullOrUndefined(body)) {
      return body;
    }

    return {};
  }

However, the generic type becomes useless in this scenario since we only receive JSON data. Any methods or properties unique to the generic object will be lost. To address this issue, we have implemented the ability to pass a constructor function to properly create the object:

private processResponse<T>(response: Observable<Response>, ctor: IParameterlessConstructor<T> | null = null): Promise<T> {
    let mappedResult = response.map(this.extractData);

    if (ctor) {
      mappedResult = mappedResult.map(f => {
        const newObj = JsObjFactory.create(f, ctor);
        return newObj;
      });
    }

    const result = mappedResult.toPromise();
    return result;
  }

The JsObjFactory implementation is as follows:

export class JsObjFactory {
  public static create<T>(source: any, ctorFn: IParameterlessConstructor<T>): T {
    const result = new ctorFn();
    this.mapDefinedProperties(source, result);

    return result;
  }

  private static mapDefinedProperties<T>(source: Object, target: T): void {
    const properties = Object.getOwnPropertyNames(target);

    properties.forEach(propKey => {
      if (source.hasOwnProperty(propKey)) {
        target[propKey] = source[propKey];
      }
    });
  }
}

While this approach works well for shallow objects, it falls short when dealing with properties that are complex types with constructors. Due to the lack of runtime types, the current workaround involves parsing properties and dynamically creating classes, which can be error-prone and tedious.

Considering that many developers face similar challenges, are there any existing solutions or features in TypeScript/JavaScript that could provide better assistance in this situation?

Answer №1

While I personally have a different approach, this method might align better with your requirements.

For instance:

Client.ts

export interface IClient {
    ID: number;
    Name: string;
    Transactions: ITransaction[];
    ...
}

export class Client implements IClient {
    public ID: number;
    public Name: string;
    public Transactions: ITransaction[];

    constructor(client: Partial<IClient>) {
        this.ID = client.ID || 0;
        this.Name = client.Name || '';
        this.Transactions = [];
        client.Transactions.forEach((transaction: ITransaction) => this.Transactions.push(new Transaction(transaction)));
    }

    //some methods
}

Transaction.ts

export interface ITransaction {
    ID: number;
    Amount: number;
    Date: string;
}

export class Transaction implements ITransaction {
    public ID: number;
    public Amount: number;
    public Date: string;

    constructor(transaction: Partial<ITransaction>) {
        this.ID = transaction.ID || 0;
        this.Amount = transaction.Amount || 0;
        this.Date = transaction.Date || '';
    }

    //additional methods
}

This structure delegates the responsibility of creating complex objects to the main Object (such as Client) and allows nested objects like Transaction to handle their instantiation.

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

Ways to execute the pdf.js node demonstrations?

I've been attempting to run the pdf.js node examples, specifically getinfo.js. Unfortunately, I encountered an issue that resulted in the following error: C:\Repositories\pdf.js>node getinfo.js node:internal/modules/cjs/loader:1080 thro ...

JSON arrays that are nested within one another

I am currently working on parsing some JSON data that contains nested arrays, and I'm facing difficulties extracting the data from the inner arrays within the main array. This is a snippet of how my JSON data is structured: {"TrackingInformationResp ...

"How to incorporate SortableJS into Ionic 3 Angular app with the help of the systemjs.config.js

I'm currently following the instructions on https://github.com/SortableJS/angular-sortablejs and I seem to be facing an issue with the systemjs.config.js file. My app is built using Ionic 3 and Angular 4. To address this, I created a script called sy ...

Ways to create auto-suggest recommendations that exceed the boundaries of the dialogue container

Is there a way to position autosuggest suggestions above the dialog instead of within it, in order to prevent scrolling of dialog content? Check out this sandbox example for reference: https://codesandbox.io/embed/adoring-bogdan-pkou8 ...

The undead browser refuses to display a window upon attempting to open using the open function

tools first attempt Darwin 14.3.0 Darwin Kernel Version 14.3.0 io.js v1.8.1 zombie Version 4.0.7 released on April 10, 2015 second attempt Linux ubuntuG5 3.13.0-48-powerpc64-smp node.js v0.10.38 zombie Version 3.1.0 released on March 15, 2015 comman ...

Execute a sorted operation with proper authorization

Recently, I developed a NextJs dashboard that enables Discord users to connect to their accounts. One of the main features is retrieving the user's guilds and filtering them to include only the ones where the user has either the MANAGE_GUILD permissio ...

Delaying the return statement

Similar Inquiry: JavaScript asynchronous return value / assignment with jQuery I'm looking for a prototype of a chart with a constructor, and I came up with this implementation: function Chart(file) { var chart = undefined $.getJSON(file, f ...

Stop the child component from activating the parent's onClick event

Trying to implement Material-ui within a React project, I am facing an issue with the <IconButton> component and its onClick behavior conflicting with the parent component's behavior. Specifically, I want the <IconButton> to add an item to ...

The method mongoose.connect() is not defined

Having a bit of trouble connecting to my MongoDB using Mongoose - keep getting this error. const { mongoose } = require('mongoose'); const db = 'dburl.com/db' mongoose.connect(db, { useNewUrlParser: true }) .then(() => console ...

Steps for resending an Ajax request once the previous one has been completed

Currently, I am experimenting with a basic Ajax request to a webpage by triggering it through an onclick event on a button: // 1. Create an XMLHttpRequest object var myRequest = new XMLHttpRequest(); // 2. Use the open method to request a resource from th ...

What is the method or variable called "afterShow" used for in FancyBox V4 and how does it differ from its counterpart in JQuery-FancyBox V3?

We previously utilized the V3 edition of Fancybox, incorporating our increaseImageClicks and increaseVideoClicks functions within its afterShow function: /* FANCYBOX OLD (https://web.archive.org/web/20210325170940/https://fancyapps.com/fancybox/3/docs/): * ...

Error message in vuejs: JSON parsing error detected due to an unexpected "<" symbol at the beginning

I have been trying to troubleshoot this issue, but I am having trouble understanding how to resolve it. Currently, I am using lottie-web in a project and need to set the animation parameters on an object in order to pass them as a parameter later. This i ...

Fetching locales asynchronously in nuxt.js using i18n and axios: A step-by-step guide

I am facing an issue with getting the location asynchronously. Whenever I try to implement my code, it results in a "Maximum call stack size exceeded" error. How can I resolve this issue? Previously, I attempted to retrieve the location by using the axios ...

What is the best way to transform a for loop using array.slice into a for-of loop or map function in order to generate columns and rows

Experiencing an issue with Angular8. Seeking help to convert a for loop for an array into a loop utilizing either for-of or array.map. The code in question involves passing an array of objects and the need to separate it into col and row arrays for visual ...

Trouble accessing contacts on LinkedIn

When attempting to retrieve the LinkedIn connections for a logged-in user, I used the following API Request: However, I encountered an error message: { "errorCode": 0, "message": "Access to connections denied", "requestId": "OFP0JOLOHO", "status" ...

Not all divs are triggering the hover event as expected

I am facing an issue while creating a webpage with overlapping background and content divs. The hover event works properly on the div with the class "content," but not on the div with the class "background." There is no interference with the event in the J ...

Secure API Calls with Prismic while Keeping Access Tokens Hidden

As I delve into the world of building a Vue.js web app, I find myself faced with the challenge of making calls to my Prismic repository without exposing my access token. The Rest API approach outlined here seems promising, but I'm at a loss on how to ...

Obtain the child's identification number and include it in the parent element as an ARIA

I need assistance with a JavaScript (or jQuery) function to dynamically add an 'aria-labelledby' attribute to parent <figure> elements based on the ID of the <figcaption>. I have multiple images and captions set up in <figure>&l ...

Vuejs method to showcase input fields based on form names

I am working on a Vue.js form component with multiple input fields, but now I need to split it into two separate forms that will collect different user input. Form 1 includes: Name Surname Email with a form name attribute value of form_1 Form 2 i ...

Creating a Vue2 modal within a v-for loop to display a dynamic

Recently, I've been working on implementing a vue2 modal following the instructions provided in the official Vue documentation at https://v2.vuejs.org/v2/examples/modal.html. The code snippet I have been using looks something like this: <tbody v-i ...