skip to Main Content

I’m evaluating Axios and one thing I can’t seem to figure out how to enforce that a response is JSON. From what I’ve gathered, Axios will automatically parse the JSON for us based on the content type (https://stackoverflow.com/a/65976510/1103734). However, I was hoping to actually enforce that the response is JSON (e.g. if my nginx proxy returns HTML due to a downstream error, I would want to handle that).

I noticed that the Axios request config has a responseType property, but as near as I can tell, this is not used to actually enforce an expected type is returned. Here’s an example snippet that demonstrates what I’m talking about

axios.get('http://cataas.com/cat?html=true', {responseType: "json"})
  .then(res => console.log(`Response:n ${res.data}`))
  .catch((err) => console.log(`Error: ${err}`))

Output:

Response:
 
                <!DOCTYPE html>
                <html lang="en">
                    <header>
                        <meta charset="utf-8">
                    </header>
                    <body>
                        <img alt="GRxZb4kUHleQ3LhC" src="/cat/GRxZb4kUHleQ3LhC">
                    </body>
                </html>

The best thing I can find is to put JSON.parse in the transformResponse property, but this means that if there’s an error in parsing a response with a bad status code, I will lose that status code information in my catch.

axios.get('http://cataas.com/cat?html=true', {responseType: "json", transformResponse: JSON.parse})
  .then(res => console.log(`Responsen ${res.data}`))
  .catch((err) => console.log(`Error: ${err}`))

Output (obviously, SyntaxError does not contain any information about the response):

Error: SyntaxError: Unexpected token < in JSON at position 17

Is there a nice way to achieve what I want?

2

Answers


  1. Chosen as BEST ANSWER

    I think I've found a way to do what I want

    import axios, { AxiosResponse } from "axios";
    
    class BadResponseFormatError extends Error {
        constructor (public response: AxiosResponse) {
            super("Malformed response");
        }
    }
    
    axios.interceptors.response.use(
        (response: AxiosResponse) => {
            if (response.headers["content-type"] !== "application/json") {
                throw new BadResponseFormatError(response);
            }
    
            try {
                response.data = JSON.parse(response.data);
                return response;
            } catch {
                throw new BadResponseFormatError(response);
            }
        }
    )
    
    axios.get('http://cataas.com/cat?html=true', {responseType: "json", transformResponse: (body) => body})
      .then((res) => console.log(`Got response with data ${JSON.stringify(res.data)}`))
      .catch((err) =>  {
            // This could also be moved to a response interceptor,
            // I just did it here for the sake of demonstration
            if (err instanceof BadResponseFormatError) {
                console.error(`Got a bad format response with status code ${err.response.status}: ${err.response.data}`)
            } else {
                console.error(`Got some other error: ${err}`)
            }
        }
      )
    
    

    A brief summary of what's going on

    1. I'm using transformResponse doing (body) => body, as presented in this answer. This allows the response interceptor to actually get at the textual response data. This was the key to make this work.
    2. I then delay the actual parse to the response interceptor, which allows me to error handle the parse manually.
    3. From there, I can create a custom exception that contains the original response, which I then use in my error handling.

  2. I think there is some confusion about the term "JSON"

    I think what you mean is that you want the result from Axios to be a Javascript object, not a JSON string. The confusion is common because we often call Javascript objects "JSON objects" as a slang term.

    If you type the following into the console, the resulting value of a will be a Javascript object:

    const a = { x: 10}
    

    Some people would call a a JSON object, but strictly speaking it is not. The JSON representation of a is the following string:

    { "x": 10 }
    

    What Axios returns to you_ not a JSON string, but a Javascript object

    This contains various pieces of information, in different properties of the object. Important to us here are:

    • The "data" property, which may be a string containing HTML, or a Javascript object, or something else.

    • Within the "headers" property, the "content-type" subproperty. This will begin with "application/json" if data is a Javascript object, and "text/html" if data is an HTML response.

    Here is your code showing the content-type of the server response explicitly.

    axios.get('http://cataas.com/cat?html=true')
      .then(response => {
        console.log("Example of an API returning an HTML response")
        const contentType = response.headers["content-type"];
        const data = response.data;
    
        console.log("Type of response data is:", contentType)
        console.log("Because it is a long string, I am just going to show a few characters of it:", data.slice(0, 40))
    
      })
      .catch((err) => console.log(`Error: ${err}`))
    
    
    axios.get('https://dummyjson.com/products/1')
      .then(response => {
        console.log("Example of an API returning an JSON response")
        const contentType = response.headers["content-type"];
        const data = response.data;
    
        console.log("Type of response data is:", contentType)
        console.log("Because it is a small object, I am going to show it all:", data)
    
      })
      .catch((err) => console.log(`Error: ${err}`))
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.3.4/axios.min.js" integrity="sha512-LUKzDoJKOLqnxGWWIBM4lzRBlxcva2ZTztO8bTcWPmDSpkErWx0bSP4pdsjNH8kiHAUPaT06UXcb+vOEZH+HpQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    The http://cataas.com/cat?html=true API returns an HTML string

    Axios faithfully gives you that string in the data property.

     <!DOCTYPE html>
                    <html lang="en">
                        <header>
                            <meta charset="utf-8">
                        </header>
                        <body>
                            <img alt="ltBmKwnyGcagdHo3" src="/cat/ltBmKwnyGcagdHo3">
                        </body>
                    </html>
    

    The https://dummyjson.com/products/1 API returns a JSON string to Axios

    Axios automatically converts that JSON string into a Javascript object for you.

    {"id":1,"title":"iPhone 9","description":"An apple mobile which is nothing like apple","price":549,"discountPercentage":12.96,"rating":4.69,"stock":94,"brand":"Apple","category":"smartphones","thumbnail":"https://i.dummyjson.com/data/products/1/thumbnail.jpg","images":["https://i.dummyjson.com/data/products/1/1.jpg","https://i.dummyjson.com/data/products/1/2.jpg","https://i.dummyjson.com/data/products/1/3.jpg","https://i.dummyjson.com/data/products/1/4.jpg","https://i.dummyjson.com/data/products/1/thumbnail.jpg"]}
    

    One way to achieve what you want:

    • Read response.headers["content-type"]

    • If it begins with application/json, then you are in luck: just treat response.data as a Javascript object

    • If it begins with text/html, despite you having requested a JSON, then something has gone wrong. You could read response.data as HTML, and look for whether the server said anything helpful.

    I don’t like the idea of wrapping everything in a try/catch, and picking up a failed JSON.parse. We are already being given information on whether response.data is an object or not, so let’s use that.

    You could even write a wrapper for Axios

    That could do the above, so you only have to write the code once.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search