Check API response data with interface in Typescript Angular

14,161

Solution 1

As you already noticed, an interface is purely TypeScript, it doesn't exist in JavaScript and is not emulated in any way (just like private, protected, public, and some other things). The type checking in TypeScript just allows to have a "fail fast" logic (you don't have to run the app to notice that your own code or a third party library is being misused, it fails at compilation time).

Your Angular app is just an UI, there are no critical operations involved (they should live in your backend), so I don't think it's a good idea to throw an error because of a type mismatch, instead of just trying to satisfy the user as much as possible. According to me a backend should validate a client input, and a client should try to adapt with a backend service.

That being said, there is a draft for JSON validation here https://json-schema.org/ and some libraries implement it (e.g: https://github.com/epoberezkin/ajv). This would allow you to get the desired behavior.

Solution 2

There are 3 questions in your issue plus one additional question by me (at the end) for a better understanding :

I'm asking this because if I have an endpoint that returns 100 attributes I'll have to check the 100 by hand, right?

This is the right way to write interfaces based on returned models from API responses. if one endpoint returns a model containing 100 properties, the right way is writing relevant Typescript interface with the same properties perhaps by a tool like json2ts. This enables type-checking and prevents unwanted programming mistakes without any cost since interfaces do not exist in runtime.

Is there any way to check if an API response is equal to the interface and throw an error if is not. Or what I'm asking isn't possible?

You can check a key property value inside tap to detect condition (null).

I was expecting that if the response from the server didn't match the interface then the Angular would redirect to catchError. But doesn't happen.

interface mismatch wont be considered as RxJS's catchError. It fires when a request error occurs like 400 (bad request). Note that Type Checking means Once you declare a variable to be a certain type, it’s the compiler job to ensure that it is only ever assigned values of that type (or values that are sub-types of that type).

Is there a way to have dynamic model for all endpoints?

Some people employ bracket notation to extract the response values. Bracket notation and dot notation are functionally equivalent in JavaScript but are not the same thing in TypeScript. TypeScript is less strict with the bracket notation and this behavior is intentional to offer some kind of backdoor. I don't propose this way which makes the code dirty and fallible.

Solution 3

You can use promise and thenable() function to catch the error if your api response is not as per your requirement.

login(user: string, password: string){
        return this.http.post('https://localhost:44344/api/auth', { user: user, pwd: password })
        .pipe(map(response => {
            this.checkForValidResponse(response)
            .then( onResolve =>{
                //Came here is your response is according to your requirements
            })
            .catch(onReject =>{
                //Catch the error and treat it as error if your resposne is not according to your requirements
            });
        }));            
    }
    checkForValidResponse(responseOfApi){
        return new Promise((resolve, reject)=>{
            //Your Code for To Check wether API response is Valid OR not . 

            //If YOur API reponse is valid then **
            resolve(Your message to send back)

            //IF your API resopnse is invalid
            reject(Your message to send back)
        })
    }

Hope this is what you are looking for .

Solution 4

Use io-ts. It gives you static typing in the IDE and runtime validation from a single definition. This is tricky to get otherwise. You otherwise have to write concrete implementations and type definitions, plus the machinery to do the validation.

Solution 5

To check if an API response is valid, use a class instead of an interface and give it an isValid function, and/or check the data in the constructor.

e.g.

interface IAuthResponseData {
  token: string;
  expires: string;
  role: string;
}

class AuthResponseData implements IAuthResponseData {
  public token: string;
  public expires: string;
  private __expires: Date;
  public role: string;

  public constructor(data: IAuthResponseData) {
    this.token = data.token;
    this.expires = data.expires;
    this.__expires = new Date(data.expires);
    this.role = data.role;
    if (!this.isValid()) {
      throw new Error("Invalid Parameters")
    }
  }

  public isValid(): boolean {
    // simple equals check against null is also true when the value is undefined
    for (let member in this) {
      if (this[member] == null) {
        return false;
      }
    }
    return true;
  }
}

Remember to catch the error and act on it, or leave the check out of the constructor and instead check after you created the object and then act on it.

Share:
14,161
Proz1g
Author by

Proz1g

Updated on June 11, 2022

Comments

  • Proz1g
    Proz1g almost 2 years

    I have an app that when the user logs in he received some data as a response, but if the response is not what I expect, the app crashes.

    Let me show you the following:

    I created an interface to tell Typescript the data I expect.

    export interface AuthResponseData {
      token: string;
      expires: string;
      role: number;
    }
    

    Then I created a method to send the login data to the server.

    login(user: string, password: string){
      return this.http.post<AuthResponseData>(
        'https://localhost:44344/api/auth', { user: user, pwd: password }
      ).pipe(
        catchError(this.handleError),
        tap(resData => { this.handleAuthentication(resData.token, resData.expires, resData.role)})
      );
    }
    

    I was expecting that if the response from the server didn't match the interface then the Angular would redirect to catchError. But doesn't happen.

    Now my question is: Is there any way to check if an API response is equal to the interface and throw an error if is not. Or what I'm asking isn't possible?

    UPDATE:

    After searching I discover that the interfaces disappear on run time, so there is no way to compare the api response with the interface (to the best of my knowledge). But I keep searching for a way to check the api response in a dynamic way. I think it is not really secure depending on api response to be always right. So I should check the API response. How can I do that?

    Hope you can help me, thanks :)

  • Proz1g
    Proz1g over 4 years
    Hi, thanks for your answer. But in this case I have always to write down all the attributes by hand. There is no dynamic way to do this? I'm asking this because if I have and endpoint that returns 100 attributes I'll have to check the 100 by hand, right?
  • CGundlach
    CGundlach over 4 years
    I've found a library that supposedly can validate this: npmjs.com/package/ts-interface-checker
  • CGundlach
    CGundlach over 4 years
    You can use the webapp quicktype.io to quickly create TypeScript interfaces out of JSON data. They also have an option to include runtime validation, as I just noticed.
  • CGundlach
    CGundlach over 4 years
    I've also modified the isValid-function in my example to iterate over all members in the class, although that would still require you to either assign the values from the interface manually in the constructor, or declare the variables with a default value of undefined and iterate over them like in the isValid function in the constructor, because otherwise they're not members of the object in the resulting JavaScript and won't be checked.
  • Proz1g
    Proz1g over 4 years
    Thanks! I'll test that as soon as possible ;)
  • Amirhossein Mehrvarzi
    Amirhossein Mehrvarzi over 4 years
    @Proz1g A class is unsuitable for declaring a type that represents an HTTP response because the deserialized JSON values that result from HTTP requests will never be instances of a class. An interface is perfect candidate for it.
  • Proz1g
    Proz1g over 4 years
    Thanks for you answer :) Unfortunately the way to check the API response with an Interface isn't good for me. I wanted something dynamic but I think it is not possible.
  • Proz1g
    Proz1g over 4 years
    Ok thanks for the notice @AmirhosseinMehrvarzi. It would be great if when I specify the response type the Angular checked for me the response and throw me an error if it wasn't a match. But that don't exists and I can't check manually ( it will be anti-productive) so I'm screwed xD.
  • Proz1g
    Proz1g over 4 years
    Wow thanks for the detailed answer :D. Just one thing: when I said I'm asking this because if I have an endpoint that returns 100 attributes I'll have to check the 100 by hand, right? I was not talking about the Interface because I know I have to do that manually. What I meant was that I had to write again all attributes and check the type for each one manually. What I really wanted was a way to compare the response with the (manually inserted) interface I created before. Anyway, I appreciate your answer, it helped me understand some basic things :) :) :)
  • Fahd Lihidheb
    Fahd Lihidheb over 4 years
    what do you mean by dynamic ? something like MyType boo = res throws an error if res is not instance of MyType ?
  • Proz1g
    Proz1g over 4 years
    In that particular case I wanted to check if the res had the same attributes and the same type as the attributes in AuthResponseData. Something like (pseudo-code): res == AuthResponseData or res instanceof AuthResponseData
  • Fahd Lihidheb
    Fahd Lihidheb over 4 years
    The closest thing you can do is to change AuthResponseData to a class and instantiate a new response from res. then typedRes instanceof AuthResponseData will always return true regardless of original res's structure. which is useless i gess :p
  • Proz1g
    Proz1g over 4 years
    yeah :´/ . But well, I had to ask :D :D Thanks any way ;)
  • Amirhossein Mehrvarzi
    Amirhossein Mehrvarzi over 4 years
    You can specify possible responses at server-side otherwise check its key properties at client-side. In general, If a key property is enumerated, other keys should be enumerated too. unless you have invalid data returned or violation on server-side. It may solve your problem :)
  • Proz1g
    Proz1g over 4 years
    Thanks for the explanation. I think you are right, I can't verify everything that comes from the backend, instead I've to adapt like you said ;)
  • Proz1g
    Proz1g over 4 years
    Hi, thanks for your answer, but the issue here is the code to check the API response is valid or not, but I already know that there isn't a pre-build method that checks it for me, because the interface is purely Typescript and disappears on runtime.
  • Proz1g
    Proz1g over 4 years
    Thanks, I'll check it out later ;)
  • Akshita Karetiya
    Akshita Karetiya over 4 years
    yupp so therefore you can check it by yourself, and validate that your response is according to you or not.
  • aycanadal
    aycanadal about 4 years
    you should definitely verify everything that comes from the backend. this allows you to detect breaking api changes immediately. it is an absolute necessity. too bad it doesn't work that way.