Transform an array of generic elements into an array of TypeScript classes

I need assistance with incorporating a typescript class:

export class Vehicle {
  constructor(
    id: string,
    makeId: string,
    makeName: string,
    modelName: string,
  ) {
    this.id = id;
    this.makeId = makeId;
    this.makeName = makeName;
    this.modelName = modelName;
  }

  public id: string;
  public makeId: string;
  public makeName: string;
  public modelName: string;
}

Afterwards, I am trying to execute an axios post request:

var results = await axios({
  method: "post",
  url: `${api}/api/vehicles`,
  responseType: "json",
  data: {
    searchTerm: searchTerm
  }
});

The response from this post includes the following structured json object:

results: {
    data: {
        suggestions: [
            {
                data: {
                    id: "93dbae75-cf32-11e9-904a-88d7f67d5c52",
                    makeId: "8641d37e-cf1e-11e9-904a-88d7f67d5c52",
                    makeName: "Audi",
                    modelName: "TT RS Coupe"
                },
                value: "(2012) Audi - TT RS Coupe"
            },
            {
                data: {
                    id: "93dcc3f4-cf32-11e9-904a-88d7f67d5c52",
                    makeId: "8641d37e-cf1e-11e9-904a-88d7f67d5c52",
                    makeName: "Audi",
                    modelName: "TT RS Coupe"
                },
                value: "(2013) Audi - TT RS Coupe"
            },
            {
                data: {
                    id: "72c4afcb-cf32-11e9-904a-88d7f67d5c52",
                    makeId: "862fba2f-cf1e-11e9-904a-88d7f67d5c52",
                    makeName: "Acura",
                    modelName: "RSX"
                },
                value: "(2003) Acura - RSX"
            },
        ]
    }
}

I am struggling with mapping this received json data into an array of my typescript class. My attempt so far has been:

for(let result in results.data.suggestions) {
  vehicles.push(result.data:Vehicle);
}

However, it seems to be reading the result as a string due to the declaration using let result. Upon inspection via console.log, I can see that it is displaying the array index.

Answer №1

The issue at hand stems from the misuse of for-in to iterate through the array entries, as that is not the intended functionality of for-in. Refer to this question's answers for further clarification; you may actually require for-of. Additionally, using a colon does not imply type assertion; instead, consider using <Vehicle>result.data or result.data as Vehicle.

for (const {data} of results.data.suggestions) {
  vehicles.push(data as Vehicle); // Alternatively, consider using `data as unknown as Vehicle`
}

Another issue arises if you follow your current approach - the objects will not be instances of Vehicle, albeit they might be compatible from a duck-typing standpoint. This works temporarily, but might create issues when adding methods to Vehicle in the future.

To tackle this, here are some potential solutions:

  1. Transform Vehicle into an interface instead of a class.

  2. Revamp the constructor of Vehicle to accept a Vehicle-like object rather than individual parameters (possibly via destructuring).

  3. Overload the Vehicle constructor to accommodate a Vehicle-like object and initialize the instance accordingly.

#1 can be implemented like so:

interface Vehicle {
  id: string;
  makeId: string;
  makeName: string;
  modelName: string;
}

Proceed with the loop as mentioned above:

for (const {data} of results.data.suggestions) {
  vehicles.push(data as Vehicle); // Or opt for `data as unknown as Vehicle`
}

#2 manifests as follows:

interface VehicleLike {
  id: string;
  makeId: string;
  makeName: string;
  modelName: string;
}
export class Vehicle {
  // *** Pay attention to the { and } within the parameter list
  constructor({
    id,
    makeId,
    makeName,
    modelName
  }: VehicleLike) {
    this.id = id;
    this.makeId = makeId;
    this.makeName = makeName;
    this.modelName = modelName;
  }

  public id: string;
  public makeId: string;
  public makeName: string;
  public modelName: string;
}

Followed by:

for (const {data} of results.data.suggestions) {
  vehicles.push(data as VehicleLike); // Or perhaps try `data as unknown as VehicleLike`
}

#3 takes shape like this:

interface VehicleLike {
  id: string;
  makeId: string;
  makeName: string;
  modelName: string;
}
export class Vehicle {
  constructor(vehicle: VehicleLike);
  constructor(
    id: string,
    makeId: string,
    makeName: string,
    modelName: string,
  );
  constructor(
    x: string|VehicleLike,
    makeId?: string,
    makeName?: string,
    modelName?: string,
  ) {
    if (typeof x === "string") {
        this.id = x;
        this.makeId = makeId as string;
        this.makeName = makeName as string;
        this.modelName = modelName as string;
    } else {
        this.id = x.id;
        this.makeId = x.makeId;
        this.makeName = x.makeName;
        this.modelName = x.modelName;
    }
  }

  public id: string;
  public makeId: string;
  public makeName: string;
  public modelName: string;
}

Then proceed as per usual:

for (const {data} of results.data.suggestions) {
  vehicles.push(data as VehicleLike); // Alternatively, consider `data as unknown as VehicleLike`
}

A heads-up: If you maintain Vehicle as it currently stands without implementing any changes listed above, and should you desire, the following code snippet mirrors your existing Vehicle while reducing redundancy:

export class Vehicle {
  constructor(
   public id: string,
   public makeId: string,
   public makeName: string,
   public modelName: string,
  ) {
  }
}

This feature is provided by TypeScript for convenience.

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 layout featuring nested buttons and links within a card element, utilizing the power of Link in NextJs

After extensive searching on S.O., I have been unable to find a solution that works flawlessly. The issue at hand involves a card component in a NextJs application that is encompassed within a <Link> tag. Additionally, there is another <Link> t ...

What steps should I take to deploy my Node.js (using TypeScript) backend on Vercel?

After meticulously designing the backend of my MERN application using TypeScript and configuring it to perfection, here is the hierarchy of my project: https://i.sstatic.net/DdOkqEe4.png Here's a glimpse of my tsconfig.json. { "compilerOptions": { ...

Introducing cutting-edge intellisense for Typescript Vue in VSCode, featuring automatic import functionality specifically designed for the

Currently, I am working on a small project using Typescript and Vue in VSCode. In my setup, I have TSLint, TSLint Vue, Vetur, and Prettier plugins installed. Unfortunately, I am facing an issue with the intellisense "auto import" feature. It does not seem ...

Obtaining a specific value from an array of objects

I am currently working with an array of objects that contain keys a, b, and c. Key a has a property named property1, key b has properties property1 and property2, and key c has a property called propertycheck. My goal is to retrieve only the key that doe ...

Creating a TypeScript class that includes an input interface with the ability to have optional

Struggling with the compiler here and wondering if I'm on the wrong track or just pursuing a misguided approach. My goal is to have a class with required parameters, along with an input interface containing optional parameters. If a parameter is miss ...

Retrieving an image from a JSON file based on its corresponding URL

I am trying to extract the URL and display it as an image: This is how it appears in JSON: https://i.sstatic.net/vpxPK.png This is my HTML code: <ul> <li *ngFor="let product of store.Products"> <p>Product Image: {{ product.Pr ...

What is a creative way to design a mat-radio-group without traditional radio buttons?

I am looking to create a component that offers users a list of selections with the ability to make only one choice at a time. The mat-radio-group functionality seems to be the best fit for this, but I prefer not to display the actual radio button next to t ...

Once StoreModule.forFeature(...) has been included, the stored data becomes inaccessible

I am currently working on multiple projects within a single Angular 8 app... Previously, I had @ngrx/store implemented in only one project, but now I have added @ngrx/store to every project. Due to having multiple stores, I now need to import StoreModule.f ...

Wildcard routes taking precedence over other defined routes

Currently, I'm developing a Node.js server utilizing Express.js and Typescript. Within my project structure, there is a folder named "routes" where I store .ts files containing route definitions. An example of a route file might appear like this: impo ...

Error message stating: "The 'MktoForms2' property is not recognized within the scope of 'Window & typeof globalThis'."

Encountering the following error message: (Property 'MktoForms2' does not exist on type 'Window & typeof globalThis') while working with react and typescript useEffect(() => { window.MktoForms2.loadForm("//app-sj11.marke ...

Ways to retrieve a URL from the assets folder

I need to establish a baseUrl for my backend requests within the assets folder. For this, I have created a server configuration file named config.json { "backendServer": { "protocol": "http", "host": " ...

Maximizing the functionality of rowDoubleClick in Angular for consistent use across various components with shared ag-grid instances

I have implemented an ag-grid in 4 different Angular Components. My goal is to only enable the rowDoubleClicked functionality for one specific component. Unfortunately, when I apply this feature to the grid, it becomes enabled for all components. How can ...

Utilize the v-for Directive in Vue-Tables-2 Templates

I am looking to showcase the data fetched using axios in my dynamically keyed datatables by creating a template. Data Set : "environment": "production", "version": "5.6", "apache_version": "3.2.1" ...

Typescript encountering issues with boolean truthiness narrowing functionality

I've searched for similar queries but couldn't find any identical to mine. My problem arises when I try to use a function to narrow down a boolean option in an if/else statement, as it only seems to work when explicitly defined and not through th ...

Dynamic tag names can be utilized with ref in TypeScript

In my current setup, I have a component with a dynamic tag name that can either be div or fieldset, based on the value of the group prop returned from our useForm hook. const FormGroup = React.forwardRef< HTMLFieldSetElement | HTMLDivElement, React. ...

Can we restrict type T to encompass subclasses of K, excluding K itself?

Can a generic type T be restricted to the subset of subtypes of type K, excluding K itself? I am attempting to define a type for inheritance-based mixin functions. An answer for the opposite case is provided in Question 32488309, and interestingly, this qu ...

The precise Typescript type for the return value of HOCs in React that inject nested props

Do I have the correct typing for an API Higher Order Component (HOC)'s return type in this specific scenario? I am using an Authentication HOC called withAuthentication, which injects auth services into a component's props. I also have an A ...

Error: Conversion of "2018-01-01-12:12:12:123456" to a date is not possible for the 'DatePipe' filter

<td>{{suite.testSuiteAttributes && suite.testSuiteAttributes.modifiedTimestamp | date: 'yyyy-MM-dd' }} </td> I am trying to display the date in the "05-Feb-2018 11:00:00 PM CST" CST format, but I keep getting an ...

What is the best approach to creating multiple dropdowns in ant-design with unique options for each?

It seems like I may be overlooking a simple solution here. Ant-Design dropdowns utilize an array of ItemProp objects to show the options, but this restricts me to having only one list of options. const choices: MenuProps['items'] = [ { label: ...

The Relationship between Field and Parameter Types in TypeScript

I am currently working on a versatile component that allows for the creation of tables based on column configurations. Each row in the table is represented by a specific data model: export interface Record { attribute1: string, attribute2: { subAt ...