15

I'm wondering what the best way is to map the http response from a get request to a class instead of a basic Javascript object.

In my current attempt I simple do new ClassName(data), but there might an obscure Angular specify and completely awesome way to do this that I don't know.

Here's my current code:

getPost(id:number){
    return this._http.get(this._postsUrl+'/'+id)
                .map(res => new Post(res.json().data))
                .do(data => console.log(data))
                .catch(this.handleError);
}

I need Post to be a class and not just an interface because I have methods inside.

I followed the HeroTutorial and the http "developer guide" along and in their getHeroes method they do:

getHeroes () {
return this.http.get(this._heroesUrl)
                .map(res => <Hero[]> res.json().data)
                .catch(this.handleError);
}

I somehow expected the <Hero[]> part to do just that: Take the Hero class and create new instances of it, but my tests show that it doesn't, this is pretty much just for Typescript to know what to expect.

Any ideas ? Thanks!

Growiel
  • 775
  • 1
  • 7
  • 20
  • 4
    This isn't related to Angular or Http, but only to TypeScript => how to deserialize JSON to a concrete class instance. – Günter Zöchbauer Feb 23 '16 at 09:30
  • 2
    `new ClassName(data)` is completely valid, you can chain operators and do mapping, filtering, reducing... Observables don't care what they wrap (; – Sasxa Feb 23 '16 at 09:47
  • @GünterZöchbauer Looks like you are right, a quick search of those words did return a lot of results. None of them are really what I wanted to hear, but I'll manage something knowing that there is no good way to do it. – Growiel Feb 23 '16 at 10:04
  • Possible duplicate of [How do I cast a JSON object to a typescript class](http://stackoverflow.com/questions/22875636/how-do-i-cast-a-json-object-to-a-typescript-class) – Mark Rajcok May 18 '16 at 22:00
  • I don't consider any of the solutions on StackOverflow to be a comprehensive solution to the problem. So, I created an npm package angular-http-deserializer for this: https://npmjs.com/package/angular-http-deserializer#usage – Jeff Fischer Dec 20 '18 at 17:13

3 Answers3

17

I think that you could use the map method of JavaScript objects:

getHeroes () {
  return this.http.get(this._heroesUrl)
            .map(res => {
               return res.json().data.map((elt) => {
                 // Use elt to create an instance of Hero
                 return new Hero(...);
               });
             })
            .catch(this.handleError);
}
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
2

By setting the prototype on the object being returned I was able to get an instance of my class.

getHero () {
      return this.http.get(this._heroUrl)
            .map(response => {
                    let res = <Hero> response.json();
                    Object.setPrototypeOf(res, Hero.prototype);
                    return res;
                   })
            .catch(this.handleError);
}

My class was very simple with a parameterless constructor so this worked for me. For more complex classes I don't know if this would work.

Note: the above code is for getHero() not getHeroes(). I would assume I could do the same thing with a list by setting the prototype on each item in the array but I haven't tried/confirmed that.

Reference: I got the idea for this from this post by BMiner

Community
  • 1
  • 1
spottedmahn
  • 14,823
  • 13
  • 108
  • 178
  • 1
    This solution in particular suffers from poor performance, which I describe in my npm package documentation. https://github.com/windhandel/angular-http-deserializer#wrong-or-partial-answers – Jeff Fischer Dec 20 '18 at 17:12
0

Good practice is to consume data from GET response using

Observable<Model>

(regarding to Angular documentation https://angular.io/guide/http) So...

// imports

import {HttpClient} from "@angular/common/http";

// in constructor parameter list

private http: HttpClient

// service method

getHeroes(): Observable<Hero[]> {return this.http.get<Hero[]>({url}, {options});}

You do not need to do anything more. I consider this approach as most friendly.

Przemek Struciński
  • 4,990
  • 1
  • 28
  • 20
  • 4
    This does not attach the prototype and methods to the object. This is using Hero as an interface, not a class with methods on it. – Matt Westlake Jan 15 '20 at 16:02
  • suppose there is a class with attributes and methods how does this code will instantiate that object ? – Neo Murphy Dec 20 '22 at 10:44