4

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(`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?

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. 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. 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? 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? 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 Commented Mar 19, 2023 at 21:07

2 Answers 2

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.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 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.
Sign up to request clarification or add additional context in comments.

3 Comments

You can just set : axios.defaults.transitional.silentJSONParsing = false; Will throw error on json parsing
Wow, that's a much cleaner solution than what I did! I would definitely change the answer to yours if you posted it :) To be clear: with this setting off, would a response of type text/html fail as well?
with responseType: "json" and silentJSONParsing = false will allow only a valid json and throw error on everything else ... like response "ok"... Full condition: github.com/axios/axios/blob/…
3

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.

1 Comment

Yeah, sorry for the confusion there. This is closest to what I want, but not quite; I was hoping that Axios could handle this in such a way that each individual callsite to .get wouldn't have to handle the content-type header. I may have found some trickery involving interceptors that I will post as a separate answer. N.B: Thanks for dummyjson.com, didn't know about that.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.