Handling a Null Response from an API

I’ve been working with React Native lately, which uses the Fetch API for asynchronously fetching data from web services.

Recently, I came across a really strange error. Certain calls (in particular, calls to fetch data for new users) would cause an error, “Unexpected end of JSON”.

What is the Fetch API?

Fetch works by asynchronously fetching the data and returning the data in a promise. In React Native’s implementation (and possibly others), any errors encountered in the promise chain are simply thrown and passed on to the next portion of the promise chain (so that you can attempt a retry or other processing). If there is no catch or error handler in the promise chain, the error is essentially swallowed (React Native may throw up a yellow warning).

Adding a .done() to the end of the promise chain causes any uncaught errors to be surfaced.

Debugging the Error

In this case, I kept adding console.log() statements further and further up the chain to try to understand where the error was coming from. Eventually, I arrived all the way up in the wrapper that we’d written for the Fetch API.

Our wrapper was pretty straight-forward. It simply adds our authentication token to the header of the request, sends the request and then uses response.json() to extract the data returned by the server.

When I attempted to perform the response.json() in isolation so that I could log the result, I got an “Unable to parse JSON” error. Stranger and stranger.

At that point, I opened my trusty Postman client and began debugging the API itself. Finally, I was able to see that the actual response from the server was…nothing.

The calls that were failing were all related to getting default user data. It turns out that when we have a new user, they haven’t had a chance to save this default data yet. If there’s no data to return, the API authors made the logical choice to return a null.

Unfortunately, response.json() is not able to operate on a null response. It appears that the null is converted to an empty string and an empty string is not valid JSON.

How do you handle a null response from the server?

After some discussion, we decided that returning a null was the correct action for the API in this case. That meant that we would have to handle the null on the client side.

While a null response cannot be parsed to valid JSON, it can be parsed to a string. So rather than use response.json(), I decided to use response.text() to parse the response. Then I just test the response for a length and either parse the string to JSON or return an empty object.

In the future, we’re going to look into using an HTTP status code of 204 (No Content) to denote that there is no response.

Here is our final code (somewhat truncated for brevity):

function _get(url) {
	return fetch(url, {
		headers: {
			Authorization: `Bearer ${this.accessToken}`,
		},
	})
	.then((res) => res.text())
	.then((text) => text.length ? JSON.parse(text) : {})
	.catch((error) => {
		throw error;
	});
}

16 Comments

  1. Paul on September 26, 2016 at 12:54 pm

    “Unfortunately, null is not valid JSON.”

    Really?

    Sure looks like it is according to http://www.json.org/

    Also:
    JSON.parse(‘null’)
    >> null

    • Garrett McCullough on September 26, 2016 at 2:10 pm

      Sorry, you’re correct.

      The problem is that response.json() isn’t able to parse a null response. I’ve updated the post slightly.

      Thanks for the feedback!

    • Garrett McCullough on September 26, 2016 at 2:13 pm

      Playing around a little in the console, my guess is that it’s something more like this:

      JSON.parse('') // that's an empty string
      >> Uncaught SyntaxError: Unexpected end of JSON input(…)

  2. anibal on September 26, 2016 at 3:50 pm

    const res = await timeout(15000, fetch(url, options))

    if (!res.ok) return reject(res)

    const contentType = res.headers.get(‘content-type’)

    if (contentType && contentType.indexOf(‘application/json’) !== -1) {
    res.json().then(json => {
    resolve(json)
    })
    }

  3. Park on July 2, 2018 at 1:12 pm

    ” is not an empty string,, “” is. You have only one quote. It returns unexpected end of JSON input because what you are giving it is not a valid string.

    • Garrett McCullough on July 9, 2018 at 11:11 am

      That was weird. WP decided that my '' should be displayed as a ". I edited my comment to add some code tags so that it wouldn’t convert it

  4. Ant on October 12, 2018 at 2:44 am

    I had this exact issue and your solution worked for me. Thank you.

    • Garrett McCullough on October 13, 2018 at 5:06 pm

      I’m glad it helped you. Thanks for reading!

  5. Adam on February 5, 2019 at 1:46 pm

    Thank you!

    • Garrett McCullough on February 5, 2019 at 7:27 pm

      Thanks for reading!

  6. Greg on August 28, 2019 at 11:31 am

    I like to simplify a bit:

    .then((res) => res.text().length ? res.json() : {})

    since you really don’t need to “then” chain sync code

    • Garrett McCullough on August 28, 2019 at 7:34 pm

      Thanks for reading, Greg. It’s been a while since I worked on that app but I remember there being a reason I had to do it that way. Looking at MDN, `res.text()` returns a promise

      https://developer.mozilla.org/en-US/docs/Web/API/Body/text

      • Greg on September 3, 2019 at 3:59 pm

        You’re right! That’s awfully poor naming, to call a method “text” that returns a promise. Adding to the confusion is that Promise contains a property, “length”, even though it always returns 1.

  7. Manish on May 5, 2021 at 3:52 am

    Finally, got what i needed. Thanks

  8. Olin on February 11, 2022 at 11:36 am

    Thank you!! I was going mad trying to decide how best to handle this ~~

  9. Houston on June 21, 2022 at 1:33 am

    Thank you. Clean and concise way to handle this.

Leave a Comment