Don't pretty print your API's JSON response body

We generally build APIs for automated integrations, not for humans to read them. Although you as a human will look at the raw API request/response formats at some point, the ideal case is that you'll only be doing this as a much smaller percentage of the time that the API is called.

So something that slightly annoys me is seeing APIs which always return pretty-printed JSON objects, when that's something that I can do myself, as-and-when I need it.

In fact, I've ended up doing this across many different languages that I have 8(!) posts on how to pretty a JSON string because it's so common.

This is something that's been quite clear in my mind for a while, and as I've used a couple of APIs in the not so recent past, as well as a recent discussion on a feature request for oapi-codegen, I thought it was worth writing up my thoughts.

My recommendation is that you do not send pretty-printed JSON at any point to your consumers.

Not only is it a larger message that requires a slightly increased amount of time to compress the data to be sent over the network (provided you are always compressing your responses, but it also takes more time to generate a pretty-printed JSON string than a non-pretty-printed one.

We can see some real numbers and impact by looking at the following Go benchmark:

package main

import (
	"encoding/json"
	"testing"
)

// via https://www.rfc-editor.org/rfc/rfc8259#section-13
type ComplexObject struct {
	Image Image `json:"Image"`
}

type Image struct {
	Width     int       `json:"Width"`
	Height    int       `json:"Height"`
	Title     string    `json:"Title"`
	Thumbnail Thumbnail `json:"Thumbnail"`
	Animated  bool      `json:"Animated"`
	IDs       []int     `json:"IDs"`
}

type Thumbnail struct {
	URL    string `json:"Url"`
	Height int    `json:"Height"`
	Width  int    `json:"Width"`
}

var obj = ComplexObject{
	Image: Image{
		Width:  1234,
		Height: 4567,
		Title:  "This is an image",
		Thumbnail: Thumbnail{
			URL:    "https://something.foo.bar",
			Height: 8932,
			Width:  12340,
		},
		Animated: true,
		IDs: []int{
			12,
			34,
			56,
			78,
			90,
		},
	},
}

func BenchmarkJSONEncode(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_, err := json.Marshal(obj)
		if err != nil {
			b.Fatalf("Failed to marshal object as JSON: %v", err)
		}
	}
}

func BenchmarkJSONEncodeWithPrettyPrinting(b *testing.B) {
	for i := 0; i < b.N; i++ {
		// two space indent
		_, err := json.MarshalIndent(obj, "", "  ")
		if err != nil {
			b.Fatalf("Failed to marshal object as JSON: %v", err)
		}
	}
}

From here, if we then run this, we see:

% go test -bench=.
goos: linux
goarch: amd64
pkg: x
cpu: Intel(R) Core(TM) i7-5960X CPU @ 3.00GHz
BenchmarkJSONEncode-16                           1000000              1767 ns/op
BenchmarkJSONEncodeWithPrettyPrinting-16          154684              6786 ns/op
PASS
ok      x       2.915s

Notice that this takes 3 times longer to pretty-print the JSON. That's a significant impact!

Although it may not seem huge, and yes we're still at the nanoseconds, but it really adds up as your API gets used by more and more traffic.

Now, sometimes you may want to do the work for your users, especially as you should be working to avoid your users using any old online JSON pretty-printer, so something you could do is have a ?pretty=true query parameter that can pretty-print the response where requested.

I'd still say really try and avoid it, as your consumers should be empowered to do the JSON pretty printing as-and-when they need it, but I guess it's your call.

This very likely won't be your service's massive bottleneck, but it'll definitely add up, and why not remove unnecessary work your service is doing, wasting resources + unnecessary network traffic?

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#api #json #rest.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.