How do I map http request response to my defined object in TypeScript

28,002

Solution 1

You can specify a type for get() such as an interface. Why an intermediary/additional interface may be warranted for separation of concerns in your situtation is that get() with a type will not new up an instance of class RegularUser. An intermediary/additional interface can be created with properties you expect from the server response that will be used to create an instance of your end class:

interface Foo {
  uid: number,
  first_name: string,
  last_name: string,
  token: string
}

this.httpClient.get<Foo>(
    'http://api.example.com/api/get_user'
).pipe(
    tap((receivedData: Foo) => console.log(receivedData)),
    map((receivedData: Foo) => {
        return new RegularUser(
            receivedData.uid, 
            receivedData.first_name, 
            receivedData.last_name, 
            receivedData.token);
    })
);

If you do not need to new up an actual instance of class RegularUser, it may be enough it to just have it as an interface or class with properties:

this.httpClient.get<RegularUser>(
    'http://api.example.com/api/get_user'
).pipe(
    tap((receivedData: RegularUser) => console.log(receivedData))
);

Hopefully that helps!

Solution 2

Instead of constructor, use interface which is a better alternative. So IDE can enable code autocompletion using the Language Service and wrong naming of properties can be avoided.

export interface RegularUser {
    success: boolean;
    uid: number;
    first_name: string;
    last_name: string;
    cid: number;
    rights: number[];
    token: string;
}

In service:

this.httpClient.get<RegularUser>(
    'http://api.example.com/api/get_user'
).pipe(
    tap((receivedData: RegularUser) => console.log(receivedData))
);

Solution 3

You can create your own Response interface by using:

export interface MyResponse {
  success: boolean,
  uid: number,
  first_name: string,
  last_name: string,
  cid: number,
  rights: number[]
  token: string
}

And then import it into your service:

import { MyResponse } from '../some/path/filename.ts'; 

But you can also include it in your service itself, so you can skip 'export'.

You can just use it just like your current Response interface:

(receivedData: MyResponse)

Note: you can give it any name you want (use TitleCase for consistency). You can also use names already used by Angular, which will then be overwritten, but that's not recommended. See comments.


You may have to make some properties optional (?:), otherwise you may get a red line indicating that 'some properties are missing' if not all are used:

...
success?: boolean,
...

Or, you can just remove the warning by making it type any, but that's not recommended:

(receivedData: any)
Share:
28,002
george007
Author by

george007

Having fun coding at WeNow.com to pay my bills. Jogging, reading books and watching movies to relax. Reading magazines and browsing blogs to stay up to date. Gadgets enthusiast.

Updated on July 09, 2022

Comments

  • george007
    george007 almost 2 years

    I'm getting to know Angular, TypeScript and RxJS. I have an http request that returns a JSON. In this JSON there is data that I need to construct my defined object. Let's say, that my object is this:

    export class RegularUser {
        constructor(
            public id: number,
            public firstName: string,
            public lastName: string,
            public token: string
        ) {}
    }
    

    Now, I am sending a request to some API, which returns data in this format:

    {
        success: boolean,
        uid: number,
        first_name: string,
        last_name: string,
        cid: number,
        rights: number[]
        token: string
    }
    

    I have the HttpClient service, so I thought I would do:

    this.httpClient.get(
        'http://api.example.com/api/get_user'
    ).pipe(
        tap((receivedData: Response) => console.log(receivedData)),
        map((receivedData: Response) => {
            return new RegularUser(
                receivedData.uid, 
                receivedData.first_name, 
                receivedData.last_name, 
                receivedData.token);
        })
    );
    

    But for TypeScript, the receivedData object doesn't have above-listed params. Do I have to create an interface for the API response and then map it to my RegularUser object?

  • george007
    george007 over 5 years
    Thank you for your answer. I will need an instance of the RegularUser object to share it, so your first option seems better. However, as I understand your User interface should have other properties, right?
  • george007
    george007 over 5 years
    Thank you for your answer. I understand that with this approach i will get the mapping done by Angular, but I actually need an instance of the RegularUser object to be able to share it. And I would like it to have only the params that I defined.
  • george007
    george007 over 5 years
    Thank you for your answer. So I have to create an interface. Can I do it in the service file where I have this http request? Also, the name of the interface shouldn't be Response, right?
  • Jeffrey Roosendaal
    Jeffrey Roosendaal over 5 years
    Updated, there you go.
  • Alexander Staroselsky
    Alexander Staroselsky over 5 years
    I'd add just add whatever properties you specifically need from the server response, no more no less. You can always log the response and add anything else attached to that object, but I'd personally just be specific to the data structure you need to use.
  • Alexander Staroselsky
    Alexander Staroselsky over 5 years
    Be careful with naming the interface Reponse as Angular's HttpClient Response may get accidentally pulled in by the IDE to existing HttpClient imports or if HttpClient Response is needed, it will need to be aliased if both are present in the same file.