Jeremy W. Sherman

stay a while, and listen

Why I'm Meh About JSON API

JSON API has been pretty successful at providing a framework for APIs that lets you focus on roughly the entity–relationship diagram of your data.

But I find it frustrating at some turns (too flexible!) and peculiar at others (why is it bound to just one content-type?).

My frustrations with JSON API are ultimately because it doesn’t solve the problems I have as an API consumer, and its aim of preserving flexibility results in API consumers paying the price of that in needing to deal with the foibles of a specific implementation and in manually tuning their API queries.

I find the approach taken by GraphQL more directly and usefully addresses my needs as a client developer while also necessarily, by design, minimizing requests made and data transmitted.

JSON API makes it possible to accomplish that, but it leaves the responsibility for doing so up to the client developer; GraphQL makes it possible to accomplish that, but it takes the perftuning responsibility upon itself, which makes my life as a client dev easier.

In This Article

Introduction

I spent the last couple months working on an Ember app. The backend was running the cerebris/jsonapi-resources flavor of JSON API implementation. The frontend was using Ember Data’s JSON API adapter.

It worked, but I also kept running across ugly data requests like:

1
2
3
4
5
6
7
8
9
10
11
12
this.store.findAll(
  'work-order',
  { include:
    [ 'location'
    , 'shipping-address'
    , 'credit-card'
    , 'user'
    , 'shipments.shipment-items.order-item.inventory-item.part'
    , 'order-items.inventory-item'
    , 'inventory-items.part.part-kind'
    ].join(',')
  });

When I see something like that, all I can think is, Why am I listing all this out for the computer? It should figure it out! Maybe in a year or so, Ember Data will indeed do that, but you need to do that sort of thing today, unless you want template rendering to lead to this conversation between HTMLBars and Ember Data: “render, oh crud we need some data, fetching… rerender, oh crud more‽ ok, fetching… rerender… what, more! fetching…”

But if you’re hitting the API by hand – be it manual XMLHTTPRequest preparation or curl – that leads to a bear of a URL. And parsing out the data once it arrives is also not so fun. I hope you enjoy writing JOIN logic client-side!

And how do you even find out what you can toss in that include bit? I just popped over to the backend source and nosed around. That’s fine when you have access to the backend source code, but what if you don’t? What’s JSON API got to say to that?

Well. I’m not terribly happy with JSON API’s answers – and we’ll come to those in a bit – but let’s see if we can understand where JSON API is coming from: How did JSON API end up like this, and to what end?

JSON API: Bytecount Golfing with -ility Handicaps

JSON API’s primary purpose is to minimize request count and data transmitted. It attempts to balance this against concerns for readability, flexibility, and discoverability.

Readability: Not Too Shabby

JSON API is pretty readable. Hit a site using it (most anything Ember), and check out the API requests and responses in your browser debugging tools, and you can work out pretty quickly what’s going on.

The side-car style for included objects, where you have to bounce from an ID reference in the main response to a lookup table that got sent along with it, hurts a bit here for humans: you have to do manual joins client-side. But inlining them wouldn’t play nice with the “minimize transfer” focus, so it makes sense.

The URLs asking for those included objects get pretty gnarly, though.

Discoverability: Meh

I’d say its discoverability is pretty darn poor; this is partly a result of its flexibility, but mainly a result of its not providing much in the way of standardized introspection facilities.

The most frustrating lack for me when I hear “backend is using JSON API” is not being able to hit the root of the API and crawl from there to work out the whole of the API and what it supports. This is one of the most important attributes for the usability of a RESTful API from where I stand, but JSON API drops the ball, or heck, doesn’t even pick it up in the first place: Hypertext through-and-through it simply ain’t.

Where this often comes to a head is with include; there’s no standard way to signal that this is supported by a backend. You can give it a go and see if it yells at you, though. But if it does support it, it’s not clear what you’re not/allowed to include with something until you try.

And if the backend doesn’t support include, then it’s free to unilaterally include whatever alongside the data you asked for. If the backend API is sanely versioned – and JSON API does not specify how to manage that – you’re probably fine, but if it’s not, and your JSON API client library prefers to fail eagerly rather than being liberal in what it accepts, your backend can break your frontend pretty readily. Versioning aside, that’s more an implementation issue than a spec issue, though, so we can let that slide.

So we have our answer to the question from the intro: How do you even find out what you can toss in that include bit? You don’t, or you guess, or you look up the docs or source code for the backend, or you email support. Mmm, emailing support: Definitely something I like to include smack in the middle of my development cycle.

Flexibility: Hurts Discoverability and Limits Utility of Having a Spec

There are a lot of “servers may do this, or that, or maybe that…” bits in there too, which make finding out a server uses JSON API less of a “now I know everything about it” than it could be. (Search for “MAY” and “SHOULD” in the document.)

We saw this with include, but it also comes into play with requesting only certain bits of a record (sparse fieldsets), sorting, pagination, and filtering, the latter of which is specified in its entirety as: “The filter query parameter is reserved for filtering data. Servers and clients SHOULD use this key for filtering operations.” The limited specification of filtering and sparse fieldsets seems suprising in the face of a focus on reducing the amount of data transferred: This seems very much fair game for a spec with that aim in mind, but it handwaves and throws it in the flexibility bin, instead.

This really smarts for two reasons as a client dev:

  • There’s no standard way to communicate what implementation-defined choices a JSON API backend has made.
  • There’s no requirement to make those choices uniformly across all APIs.

This again means that learning an API is using the JSON API spec doesn’t buy you as much as it could; you still have to ask a lot of questions to sort out what that means in practice.

It also means that any client-side de/serializer for JSON API is limited in the support it can provide to you. The spec is very open to customization, which means that you will have to learn those customizations in force for your backend and teach your JSON API parser about them.

This reminds me a bit of how OAuth 2 moved from being a spec to a meta-“spec”, flexible to a fault, as described by the one-time lead author and editor of that spec:

One of the compromises was to rename it from a protocol to a framework, and another to add a disclaimer that warns that the specification is unlike to produce interoperable implementations. (“OAuth 2.0 and the Road to Hell”)

Peculiar: Why Only JSON?

JSON API seems weirdly bound to the content-type (it’s an API! in JSON!), which is kind of funny to me in light of the “A server MUST prepare responses, and a client MUST interpret responses, in accordance with HTTP semantics” language. This feels like following the letter rather than spirit of the law: There’s no notion of a resource that might go by various possible representations. Content transferred under JSON API’s auspices goes by a JSON-API–specific content-type.

JSON is not a terribly expressive data format; you’ve got the rudiments needed to cobble together more specific data types atop it. That also means there’s little reason you couldn’t translate the data in a JSON API response into another content type, be it BSON, XML, S-expressions, or something even more unique.

Maybe That’s Not JSON API’s Job?

Perhaps the JSON API homepage, rather than the spec, is more honest in its aims:

If you’ve ever argued with your team about the way your JSON responses should be formatted, JSON API can be your anti-bikeshedding tool.

By following shared conventions, you can increase productivity, take advantage of generalized tooling, and focus on what matters: your application.

Clients built around JSON API are able to take advantage of its features around efficiently caching responses, sometimes eliminating network requests entirely.

It takes for granted you’re building an API, and it’s only going to support JSON. Its pitch: Use JSON API so you don’t have to quibble about how you encode your data, and you get this already thought-through support for caching data and minimizing the requests needed for free!

Perhaps JSON API’s audience is specifically API producers, not consumers, and that’s why I don’t find it addressing my needs.

How Is GraphQL Better?

The more declarative approach of GraphQL (and, to a lesser degree, Falcor) fulfills the spec-stated goals better than JSON API does itself.

Heck, it also satisfies those of the homepage better, too!

GraphQL in a Nutshell

The rough idea is:

  • There is a typed spec for what data is available and how it’s related.
  • Components can request specific bits they need using a query language. Queries can be typechecked.
  • A query builder can aggregate component requests into a more general request, coalesce them, and then hit the backend/respond from cache intelligently, without the components needing to worry about this.
  • The system then vends back to the components precisely the info they requested, no more, no less.

You can see where that’d be handy in a React world of little components asking for this or that nugget of info, which was the background against which GraphQL arose.

No Intrinsic Content-Type

The actual wire protocol is kind of beside the point unless you’re the GraphQL engine implementor. Is it sent by JSON or BSON or MP3s modulated at 56kbps? Is it using HTTP over TCP? Audio blips over SCTP? Who cares! Here’s how the data is laid out when it reaches you, here’s the types of that data; ask for what you need.

No Manual Perftuning

The query optimization bit can get arbitrarily clever without impacting the components, which is excellent for future performance tuning: Upgrade your client-side GraphQL engine, and your app stands to suddenly get more performant, without any further work on your part.

And then I go to an Ember app using JSON API where they have these insane URLs where they’ve got &include=this,that,theotherthing,ohandnowthisthing and the diffs for that insanely long line are so fun to read, lemme tell you. (I prettied up the intro code snippet to use […].join() so it’d be readable at most widths without horizontal scrolling, but that was just one big ol' string in the source. Ayup.)

It’s this very manual, rough query optimization by hand that I think it’s silly they’re needing to worry about, when they’re not even concerned about query optimization; they just need some data. And the optimization is limited by the size of the records, as well.

Conclusion

JSON API is a rather limited spec that I find flexible to a fault as a client developer. It seems wide open to bikeshedding still on the API producer’s side as well, due to that flexibility, so I’m not sure how well it meets either its spec-stated or marketing-stated aims.

GraphQL offers a declarative approach to directly expressing the data available – which addresses my desire to be able to pull that information without a lot of digging as a client dev – and the data requested – which addresses my desire to be able to pull the data I need without worrying about the details of how it’s going to get to me.

But JSON API slots neatly into an existing niche – an API! in JSON! Hey, I think I’ve used a few of those! While GraphQL is a different bird entirely. Consequently, I’ve yet to use GraphQL, while I have ended up working against a JSON API already, and expect I’ll find myself doing so again in future as well.

JSON API is an incremental improvement, serviceable and certainly no worse than even more thoroughly ad hoc API creations, and so I expect it to spread widely: I expect to run into a lot of JSON API backends, and may never have the chance to consume a GraphQL backend. I’m glad for what sanity JSON API does bring to the wild west of APIs out there.


Thanks to Chris Krycho for feedback on a draft of this article. He tripped over all the awkward transitions so you don’t have to. ;)