JSON API, OpenAPI and JSON Schema Working in Harmony

JSON API, OpenAPI and JSON Schema Working in Harmony

A regular question on Twitter, at WeWork, and in the APIs You Won’t Hate Slack community, is: "What standard should my API follow? Should it be JSON API or something else?” At which point a bunch of people usually start discussing things like OpenAPI and/or JSON Schema. In this article, I’d like to talk about how they can all work together in harmony, to do different things.

JSON API

JSON API is a specification written by a group of folks, with the goal of being an anti-bikeshedding tool for writing JSON APIs. The name often causes a bit of confusion, but JSON API is one of many data formats that is often applied to REST (or RESTish) APIs, as an alternative to Siren, HAL, Uber, etc.

The goal of JSON API is to standardize some of the specifics of API design that the REST paradigm leaves to the implementer. REST has no opinions on how you implement resources vs collections, or where meta data should go, how to include related resources, how pagination should work, or anything else, so JSON API tries to fill in a lot of those gaps for you.

Many people try to figure those things out their selves, and there are a lot of things that can go wrong. Build APIs You Won’t Hate would have been a lot slimmer if everyone had just used JSON API back then.

Firstly, JSON API explains what shape the body of the HTTP request and response should take. Specifically, where primary data goes, where meta data goes, where should links to other resources be placed, and how exactly should related data be included.

{
  "links": {
    "self": "http://example.com/articles",
    "next": "http://example.com/articles?page[offset]=2",
    "last": "http://example.com/articles?page[offset]=10"
  },
  "data": [
    {
      "type": "articles",
      "id": "1",
      "attributes": {
        "title": "JSON API paints my bikeshed!",
        "stuff": "and nonsense"
      },
      "relationships": {
        "author": {
          "links": {
            "self": "http://example.com/articles/1/relationships/author",
            "related": "http://example.com/articles/1/author"
          }
        },
        "comments": {
          "links": {
            "self": "http://example.com/articles/1/relationships/comments",
            "related": "http://example.com/articles/1/comments"
          }
        }
      },
      "links": {
        "self": "http://example.com/articles/1"
      }
    }
  ]
}

This structure initially seems like a lot of noise to somebody who is expecting an API to do one thing: transfer a few fields from the server to their client, but when an API is more of a state machine over HTTP, a lot of this starts to make sense.

Secondly, beyond just the shape of the request/response body, JSON API helps with a few other things, like Sparse Fieldsets. You can let clients pass /articles?fields[articles]=title,body to get just the title and body fields, a feature much loved by GraphQL advocates. They’re a controversial topic in the REST world because they can slim down responses, but also screw up cache ratios.

Maybe use them maybe don’t, but JSON API is there to give you the option for this. It helps out with a lot of other things, like ?includes= for compound documents, it takes a stab at pagination, makes a vague hand-wave at filtering, and covers things like error objects for those not using RFC 7807.

All of this is basically very structural, but none of it tells you anything about what the data is, or anything about the data. There is no "schema” functionality (or types, as some folks call them). For that, you need to look at something like OpenAPI, or JSON Schema.

OpenAPI and JSON Schema

OpenAPI and JSON Schema are two different specifications that have inspired each other a lot, but are subtly different.

OpenAPI aims to describe both the service model (the API in general, endpoints, request metadata like headers, authentication strategies, response metadata, etc.), and it also covers the HTTP request/response body using a bunch of keywords based on JSON Schema, that have diverged over time. The divergence is slowly being solved.

JSON Schema aims to describe an instance of JSON data, like the ones found in a HTTP request or response, but is in no way limited to a HTTP API.

In describing the data, you can say which fields are required, mention common formats like email, UUID, etc., add more complex validation rules to those fields like maximum length, or regex patterns, and all sorts of other functionality. The data could be in any shape, but whatever data is there can be described with one of these two tools.

At WeWork, we use JSON Schema to describe the data models, OpenAPI to describe everything else, then the message being described is usually JSON API.

OpenAPI has a lot of design-time and build-time tooling, so it’s popularly used for mocking services and generating SDKs. It is not commonly used for run-time functionality. For that, most folks use JSON Schema, which can do many amazing things like client-side validation.

Seeing as JSON Schema does not touch the service model, it’s quite tough to build things like SDK generators as they do not know anything about the endpoints. There could be a future where JSON HyperSchema can take over this role from OpenAPI, as a fully RESTful API with JSON HyperSchema would be able to build a HATEOAS compatible SDK, which would focus on navigating from root, to resource, to resource, instead of worrying about memorizing endpoints in the first place.

JSON API and JSON HyperSchema can actually complement each other quite well. Aaron Hedges recently wrote about how JSON HyperSchema can be used to describe JSON API payloads, taking the plain old "here is a HTTP link do with it what you will” approach to HATEOAS, and peppering it with all sorts of useful information.

Some folks refer to this as upgrading an APIs "Hypermedia Maturity Model”, taking JSON API from HMM 1 to HMM 2. More on the Hypermedia Maturity Model here.

Summary

The point here is that these three things all have rather different jobs, and can be used together. You don’t want multiple teams cranking out handfuls of REST APIs with completely different payload shapes and completely different approaches to things, and whatever shape the API data is in, you need to know more about what that data actually is. Think of one as a "Data Format” and another as a "Data Contract” and things make a bunch more sense.

I’ve used JSON API extensively for years, flipping from being a big fan to an outspoken critic, and back again multiple times. JSON API is a great way to solve a lot of problems in a HTTP/1 world, but HTTP/2 should handle a lot of the things that JSON API focuses on. That said, a lot of tooling (especially in the Ruby world) is still completely ignoring HTTP/2. I recommend JSON API as an alternative to anarchy, but it is full of potential foot-guns, like any powerful concept used carelessly. Please read Making the Most of JSON API if you are considering using it, and do not take my mentioning of it here as tacit approval for every idea in the specification.

The cover photo is an awesome Zelda Triforce Lamp which I would absolutely buy if I lived somewhere.