Formatting and Enveloping Responses
So far in this book we’ve generally been making requests to our API using Firefox, which makes the JSON responses easy-to-read thanks to the ‘pretty printing’ provided by the built-in JSON viewer.
But if you try making some requests using curl, you’ll see that the actual JSON response data is all just on one line with no whitespace.
We can make these easier to read in terminals by using the
json.MarshalIndent() function to encode our response data, instead of the regular
json.Marshal() function. This automatically adds whitespace to the JSON output, putting each element on a separate line and prefixing each line with optional prefix and indent characters.
Let’s update our
writeJSON() helper to use this instead:
If you restart the API and try making the same requests from your terminal again, you will now receive some nicely-whitespaced JSON responses similar to these:
json.MarshalIndent() is positive from a readability and user-experience point of view, it unfortunately doesn’t come for free. As well as the fact that the responses are now slightly larger in terms of total bytes, the extra work that Go does to add the whitespace has a notable impact on performance.
The following benchmarks help to demonstrate the relative performance of
json.MarshalIndent() using the code in this gist.
In these benchmarks we can see that
json.MarshalIndent() takes 65% longer to run and uses around 30% more memory than
json.Marshal(), as well as making two more heap allocations. Those figures will change depending on what you’re encoding, but in my experience they’re fairly indicative of the performance impact.
For most applications this performance difference simply isn’t something that you need to worry about. In real terms we’re talking about a few thousandths of a millisecond — and the improved readability of responses is probably worth this trade-off. But if your API is operating in a very resource-constrained environment, or needs to manage extremely high levels of traffic, then this is worth being aware of and you may prefer to stick with using
Next let’s work on updating our responses so that the JSON data is always enveloped in a parent JSON object. Similar to this:
Notice how the movie data is nested under the key
"movie" here, rather than being the top-level JSON object itself?
Enveloping response data like this isn’t strictly necessary, and whether you choose to do so is partly a matter of style and taste. But there are a few tangible benefits:
Including a key name (like
"movie") at the top-level of the JSON helps make the response more self-documenting. For any humans who see the response out of context, it is a bit easier to understand what the data relates to.
It reduces the risk of errors on the client side, because it’s harder to accidentally process one response thinking that it is something different. To get at the data, a client must explicitly reference it via the
If we always envelope the data returned by our API, then we mitigate a security vulnerability in older browsers which can arise if you return a JSON array as a response.
There are a couple of techniques that we could use to envelope our API responses, but we’re going to keep things simple and do it by creating a custom
envelope map with the underlying type
Let’s start by updating the
cmd/api/helpers.go file as follows:
Then we need to update our
showMovieHandler to create an instance of the
envelope map containing the movie data, and pass this onwards to our
writeJSON() helper instead of passing the movie data directly.
We also need to update the code in our
healthcheckHandler so that it passes an
envelope type to the
writeJSON() helper too:
Alright, let’s try these changes out. Go ahead and restart the server, then use
curl to make some requests to the API endpoints again. You should now get responses formatted like the ones below.
It’s important to emphasize that there’s no single right or wrong way to structure your JSON responses. There are some popular formats like JSON:API and jsend that you might like to follow or use for inspiration, but it’s certainly not necessary and most APIs don’t follow these formats.
But whatever you do, it is valuable to think about formatting upfront and to maintain a clear and consistent response structure across your different API endpoints — especially if they are being made available for public use.