admin管理员组

文章数量:1296453

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 (). 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('', {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('', {responseType: "json", transformResponse: JSON.parse})
  .then(res => console.log(`Response\n ${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?

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./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./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./cat?html=true', {responseType: "json", transformResponse: JSON.parse})
  .then(res => console.log(`Response\n ${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?

Share asked Mar 19, 2023 at 20:43 ollienollien 4,79610 gold badges39 silver badges60 bronze badges 12
  • 2 No, there is no way to enforce a JSON response. The server can always decide to respond what it want. The client can ask for specific response type, but the server doesn't have to follow. – jabaa Commented Mar 19, 2023 at 20:46
  • Of course; I can't control the server response, but I'm looking for a client-side assertion to ensure that the data in response.data is actually JSON, and not just a string (in the example I gave above, axios does not throw an error in this case; it simply carries on). Ideally, the response would have a proper Content-Type header, and Axios could see it wasn't JSON, and throw an error. – ollien Commented Mar 19, 2023 at 20:48
  • 1 If you want to make sure the response is JSON just try/catch JSON.parse(response) and you'll know? – custommander Commented Mar 19, 2023 at 20:52
  • 1 TBH, I still don't understand what you want. The response is always a string. It can be a JSON string or something. How do you want to handle a JSON string and how do you want something else? – jabaa Commented Mar 19, 2023 at 21:06
  • 1 I want Axios to throw an error when it receives a non-JSON response (whether the content type indicates otherwise or the response is just simply not parsable as json). In other words, I want to guarantee that by the time my application code reads response.data, it is actually parsed JSON. Apologies for the confusion – ollien Commented Mar 19, 2023 at 21:07
 |  Show 7 more ments

2 Answers 2

Reset to default 4

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./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 with a value of (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.

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 mon 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./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./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./ajax/libs/axios/1.3.4/axios.min.js" integrity="sha512-LUKzDoJKOLqnxGWWIBM4lzRBlxcva2ZTztO8bTcWPmDSpkErWx0bSP4pdsjNH8kiHAUPaT06UXcb+vOEZH+HpQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

The http://cataas./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./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./data/products/1/thumbnail.jpg","images":["https://i.dummyjson./data/products/1/1.jpg","https://i.dummyjson./data/products/1/2.jpg","https://i.dummyjson./data/products/1/3.jpg","https://i.dummyjson./data/products/1/4.jpg","https://i.dummyjson./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.

本文标签: javascriptEnforce that JSON response is returned with AxiosStack Overflow