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; }); }
“Unfortunately, null is not valid JSON.”
Really?
Sure looks like it is according to http://www.json.org/
Also:
JSON.parse(‘null’)
>> null
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!
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(…)
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)
})
}
” 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.
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 itI had this exact issue and your solution worked for me. Thank you.
I’m glad it helped you. Thanks for reading!
Thank you!
Thanks for reading!
I like to simplify a bit:
.then((res) => res.text().length ? res.json() : {})
since you really don’t need to “then” chain sync code
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
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.
Finally, got what i needed. Thanks
Thank you!! I was going mad trying to decide how best to handle this ~~
Thank you. Clean and concise way to handle this.