Reinventing the wheel when encoding links in JSON
Links are critical when writing good REST APIs. Most APIs these days use JSON for their main format. Unfortunately, there’s no universally agreed on way to encode a link in JSON.
REST can really benefit from generic tooling. To write a generic tool that consumes links, it means that these tools might have to support a dozen formats or use other heuristics to find out if something is a link.
This begs the question, if there were a ‘good default’ serialization, what would that look like? Here’s my take.
The general shape
Most (but not everyone) of the API community has accepted ‘Web linking’ as the de-facto datamodel.
Broadly, this means that a link should at least have a URI (href
), a
relationship type (rel
) and optionally the following attributes:
title
hreflang
type
media
Aside from support for these attributes, I feel that for something to be a successful ‘good default’ and actually adopted it would need a dedicated IETF RFC. One that’s ideally just focuses on links and not part of a larger standard.
Existing JSON link formats
What follows is a nonexhaustive list of existing link serializations in JSON.
HAL
{
_links: {
author: {
href: 'https://evertpot.com/',
title: 'My website!',
hreflang: 'en-CA',
}
]
}
HAL links are technically both a sub- and superset of RFC8288, because it
adds features such as ‘templated’ links, but it also explicitly allows colon :
in relationship types. HAL calls these ‘curies’ but they are not compatible
with W3C CURIES, and also shouldn’t be used as such (they are not meant
to be expanded, yes this is confusing).
Collection+JSON
{
collection: {
links: [
{
rel: 'author',
href: 'https://evertpot.com/',
name: 'My website!',
}
]
}
}
Collection+JSON adds the prompt
and render
properties. It has no explicit
support for type
or hreflang
.
Siren
{
links: [
{
rel: ['author'],
href: 'https://evertpot.com/',
title: 'My website!',
type: 'text/html',
}
]
}
draft-wilde-linkset
{
linkset: [
{
// Short
author: 'https://evertpot.com/',
// Long
author: [
{
href: 'https://evertpot.com/',
title: 'My website!',
type: 'text/html',
hreflang: 'en-CA',
}
]
}
]
}
Web Thing API
{
links: [
{
rel: 'author',
href: 'https://evertpot.com/',
mediaType: 'text/html',
}
]
}
JSON:API
{
links: [
{
// Short
author: 'https://evertpot.com/',
// Long
author: {
href: 'https://evertpot.com/',
}
}
]
}
JSON:API has no standard way to encode title
, type
or hreflang
and does
not provide a way for a specific links relationship to appear twice.
draft-nottingham-json-home
{
api: {
links: {
author: 'https://evertpot.com/',
}
},
resources: {
author: {
href: 'https://evertpot.com/',
hints: {
format: 'text/html',
}
}
}
}
The json-home format has 2 places where link relationships are used, so I encoded an example for each.
There’s no way to encode title
or hreflang
, and no way for 2 resources
to have the same link relationship.
Other notable JSON links
Many API formats use a convention as follows:
{
author_url: 'https://evertpot.com/'
}
The Github v3 API uses this convention, and it’s formalized a bit more
in RESTful JSON. You can also see this convention used in the
OAuth2 discovery document, although it uses the _uri
prefix.
There is also JSON-LD and Hydra, which is a pretty popular format. I’m not 100% sure if it’s possible to encode RFC8288 links in a lossless way.
The issue with not having a good default
New formats get invented every day, and many of them will have some need to encode a link.
Using something like JSON:API or Siren - although a good choice - is a big decision. Maybe even an emotional one. These standards are larger than their formats. They’re fairly opinionated and imply a buy-in into these opinions and their ecosystems.
The result is when authors of IETF RFC’s or other standards bodies run into this road block, they tend to just invent their own, often drawing inspiration on what’s out there.
I would really like to see a simple, uncontroversial IETF RFC that just makes a good suggestion on how to encode a RFC8288 link and a set of links so the next time the groups behind OAuth2, Web Things API or specialized single-purpose formats like json-home or Linkset only have to do something unique if they have an actual unique need.
How should it look like?
Based on what everyone is doing, it feels that the least controversial way to encode a single link would look a bit like this:
{
rel: 'author',
href: 'https://evertpot.com/',
title: 'My website!',
type: 'text/html',
}
And a collection of links would just be an array of these objects:
{
links: [
{
rel: 'author',
href: 'https://evertpot.com/',
title: 'My website!',
type: 'text/html',
}
]
}
This allows links to appear in-line and and on a document-level, it can fully encode RFC8288 and it looks a lot many other things out there.
This is not really a specification, but I thought I would start with just floating this as an idea. One issue with this is illustrated well in XKCD 927, but my goal with this idea is not really coming up with a standard to unify them all. I just want something to exist for folks that don’t want to make this decision and I do think it needs the weight of a IETF RFC to make this happen.
What do you think?
- You can reply to this tweet to automatically see your response here.
- If you’re a dev, you can also send a pull request and edit in your comment in this article.