0

I have 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;
}

Next, I have an axios post:

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

This post returns the following json:

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"
            },
        ]
    }
}

How do i map this json resultset into an array of my typescript class? I've tried the following:

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

It is, however, picking up the result in let result as a string. If I console.log the result, I get the array index.

Carel
  • 2,063
  • 8
  • 39
  • 65
  • *"This post returns the following json"* Hopefully not, as that's not [JSON](https://json.org). – T.J. Crowder Nov 07 '19 at 09:29
  • axios returns `Promise>` by default, so you can just type your `var results` as `var result: AxiosResonse` and get all typings for free. No need to waste resources with a pointless loop. – georg Nov 07 '19 at 20:40

1 Answers1

1

The primary problem is that you're using for-in to loop through the array entries, and that's not what for-in does. See this question's answers for details, but you probably want for-of. Also, the colon doesn't do type assertion, for that you use <Vehicle>result.data or result.data as Vehicle.

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

The separate problem is that if you do what you've described, the objects won't be instances of Vehicle, although they will be compatible from a duck-typing perspective. That's fine for now, but if you ever give Vehicle methods, they won't have them.

You have a couple of options for addressing that:

  1. Make Vehicle an interface rather than a class.

  2. Make Vehicle's constructor accept a Vehicle-like object rather than discrete parameters (perhaps via destructuring)

  3. Overload the Vehicle constructor to accept a Vehicle-like object and populate the instance from it.

#1 is pretty obvious:

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

Then your loop is as above:

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

#2 looks something like this:

interface VehicleLike {
  id: string;
  makeId: string;
  makeName: string;
  modelName: string;
}
export class Vehicle {
  // *** Note the { and } in 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;
}

Then

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

#3 looks something 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 (again)

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

Side note: If you don't change Vehicle per any of the above and you keep it the way you have it, and if you want to, this is functionally identical to your current Vehicle, just with a lot less repetition:

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

It's a convenience feature TypeScript provides.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Thanks, that works. I just tried using the `for of` loop before your edit and confirmed that the property values are accessible. I passed each property individually to the constructor. I thank you for providing some examples where the mapping within the constructor is better. – Carel Nov 07 '19 at 09:54