230

I'm using fetch polyfill to retrieve a JSON or text from a URL, I want to know how can I check if the response is a JSON object or is it only text

fetch(URL, options).then(response => {
   // how to check if response has a body of type json?
   if (response.isJson()) return response.json();
});
2

7 Answers 7

366

You could check for the content-type of the response, as shown in this MDN example:

fetch(myRequest).then(response => {
  const contentType = response.headers.get("content-type");
  if (contentType && contentType.indexOf("application/json") !== -1) {
    return response.json().then(data => {
      // The response was a JSON object
      // Process your data as a JavaScript object
    });
  } else {
    return response.text().then(text => {
      // The response wasn't a JSON object
      // Process your text as a String
    });
  }
});

If you need to be absolutely sure that the content is a valid JSON (and don't trust the headers), you could always just accept the response as text and parse it yourself:

fetch(myRequest)
  .then(response => response.text()) // Parse the response as text
  .then(text => {
    try {
      const data = JSON.parse(text); // Try to parse the response as JSON
      // The response was a JSON object
      // Do your JSON handling here
    } catch(err) {
      // The response wasn't a JSON object
      // Do your text handling here
    }
  });

Async/await

If you're using async/await, you could write it in a more linear fashion:

async function myFetch(myRequest) {
  try {
    const reponse = await fetch(myRequest);
    const text = await response.text(); // Parse it as text
    const data = JSON.parse(text); // Try to parse it as JSON
    // The response was a JSON object
    // Do your JSON handling here
  } catch(err) {
    // The response wasn't a JSON object
    // Do your text handling here
  }
}
Sign up to request clarification or add additional context in comments.

10 Comments

@WouterRonteltap : Aren't you only allowed to do one or the other. It seems like I remember that you only get one shot at response.anything(). If so, JSON is text, but text isn't necessarily JSON. Therefore, you have to do the sure-thing first, which is .text(). If you do .json() first, and it fails, I don't think you'll get the opportunity to also do .text(). If I'm wrong, please show me different.
In my opinion you can't trust the headers (even though you should, but sometimes you just can't control the server on the other side). So it's great that you also mention try-catch in your answer.
Yes, @Lonnie Best is completely correct in this. if you call .json() and it throws an exception (because the response isn't json), you will get a "Body has already been consumed" exception if you subsequently call .text()
@Andy you can call response.clone() to get cloned instance and therefore you can call .json() or .text() multiple times. Ref: developer.mozilla.org/en-US/docs/Web/API/Response/clone
As the stevenklambert.com/writing/fetch-json-text-fallback article mentions it too, using clone(), u can just: fetch('/file') .then(response => response.clone().json().catch(() => response.text()) ).then(data => { // data is now parsed JSON or raw text });
|
21

You can do this cleanly with a helper function:

const parseJson = async response => {
  const text = await response.text()
  try{
    const json = JSON.parse(text)
    return json
  } catch(err) {
    throw new Error("Did not receive JSON, instead received: " + text)
  }
}

And then use it like this:

fetch(URL, options)
.then(parseJson)
.then(result => {
    console.log("My json: ", result)
})

This will throw an error so you can catch it if you want.

3 Comments

Very nice! It will still create a 404 error in the browser's console if the URL doesn't exist at all, but I guess it's unavoidable.
@LWC to catch a 404 (an error on the request not on the response) you will need to use a .catch on the fetch
Thanks, it already states that in the answer itself. But my comment was about the browser's console, which site visitors can see.
2

Here's an await and async answer based on @nils answer checking the content type first before calling response.json(). No cloning needed.

async function myFetch(myRequest) {
  try {
    const response = await fetch(myRequest);
    const contentType = response.headers.get("content-type");
    var data;
    if (contentType && contentType.includes("application/json")) {
       data = await response.json(); 
    }
    else {
       data= await response.text();
    }

    return data;
  }
  catch(error) {
    // other errors occurred
    throw error;
  }
}

try {
   var json = await myFetch("test");
   if (json && json.success) { 
      // do something
   }
}
catch(error) {

}

Another example that throws an error if it's not json (not tested):

async function myFetch(myRequest, noJsonFunction) {
  try {
    const response = await fetch(myRequest);
    const contentType = response.headers.get("content-type");
    var data;
    if (contentType && contentType.includes("application/json")) {
       data = await response.json();
       return data;
    }
    throw new Error(response);
  }
  catch(error) {
    throw error;
  }
}

// use
try {
   var json = await myFetch("test");
}
catch(error|response) {
   if (error instanceof Error) {}
   if (error instanceof Response) {}
}

2 Comments

FYI String.prototype.includes() is semantically clearer than indexOf()
Updated it. But then why not use contains()? Well, now I'm confused because I don't see contains() on string and I don't see it on array. I've updated it to use includes()
0

Use a JSON parser like JSON.parse:

function IsJsonString(str) {
    try {
        var obj = JSON.parse(str);

         // More strict checking     
         // if (obj && typeof obj === "object") {
         //    return true;
         // }

    } catch (e) {
        return false;
    }
    return true;
}

2 Comments

Where does the string come from?
str is a parameter and you can pass any json string as an argument. for example, we can pass the http response text here to check it is in JSON format or not.
0

Fetch returns a Promise. with Promise chain, a one liner like this would work.

const res = await fetch(url, opts).then(r => r.clone().json().catch(() => r.text()));

enter image description here

4 Comments

catch expects a function type parameter and not the return value of r.text()
Thanks @PeterSeliger for pointing out the problem in .catch(r.text()). My eslint has correct mine to .catch(() => r.text()). And I have not looked back and assumed it was working.
this was a saver
bad idea . it can be prevented to show error
-1

I recently published an npm package that includes common utility functions. one of these functions that I implemented there is just like the nis's async/await answer that you can use as bellow:

import {fetchJsonRes, combineURLs} from "onstage-js-utilities";

fetch(combineURLs(HOST, "users"))
    .then(fetchJsonRes)
    .then(json => {
        // json data
    })
    .catch(err => {
        // when the data is not json
    })

you can find the source on Github

Comments

-1

i decided to use this one

   return fetch(url).then(async k =>{
             const res = await k.text();
             if(res.startsWith("[") || res.startsWith("{")){
                return JSON.parse(res);
             }
             console.warn(url + " Did not pass Json Format");   
             return [];
            }).then(g => {          
          ////
        }) 
       

5 Comments

There is no guarantee that a string starting with {/[ is a valid JSON value, not to mention arrays and objects are not the only valid JSON values.
it is just answer to page question not risky conditions
for that you can setup a jsonContentChecker and check response letter by letter ... then change res.startsWith("{") to CheckObj(res)
It doesn't answer the question because it will throw at JSON.parse() call when the string is not in fact a valid JSON value, but just happens to start with {/[ and the console.warn() won't even trigger in that case.
as i said before it is not for risky condition that u do load from unknown sources anyone knows it should have better checker

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.