Links with custom relations

In order to improve the hypermedia-ness of my API, I’ve wanted to add links with custom relation types to my resources. It wasn’t until recently that I realised this isn’t actually allowed by the spec:

Unless otherwise noted, objects defined by this specification MUST NOT contain any additional members. Client and server implementations MUST ignore members not recognized by this specification.

While I didn’t think much of this at first, investigating issues on Github has made me realise this does in fact apply to links objects too, so something like the following would be illegal:

{
  "links": {
    "avatar": "https://static.myapp.com/avatars/foo.png"
  }
}
  1. avatar is not a registered link relation type, of course, but more importantly,
  2. the spec does not define the links.avatar member, so it’s not allowed.

Reading through this issue I found a great clarification of why adding more links is important to me (emphasis mine):

Let me start with this part of the question: what is actually the core problem here. Discoverable actions, at least the way I suggest to implement them, do not pretend to solve the problem of building full hypermedia APIs, which need little to no documentation. It is assumed that the documentation on formats of specific requests is needed. The purpose of actions is to inform the client what is available for the given user on a given resource at a given moment of time – this is why actions should be returned dynamically, as part of a resource’s representation, and not via schema, docs, or in some other static way. So to me, the core problem is different from what you describe.

I’ve since combined two pieces of knowledge to decide on a course of action that will help me continue building useful features into my API for now, but also should remain spec-compliant as far as I can discern its future direction:

RFC8288 states that “extension relation types” can be used if they are URIs. URI syntax is defined in RFC3986, and here are some examples:

     foo://example.com:8042/over/there?name=ferret#nose
     \_/   \______________/\_________/ \_________/ \__/
      |           |            |            |        |
   scheme     authority       path        query   fragment
      |   _____________________|__
     / \ /                        \
     urn:example:animal:ferret:nose

Colons are a necessary part of URIs.

Separately, JSON:API 1.1 extensions specify that extension members be namespaced using a :.

O happy circumstance! I shall proceed, for now, by defining an extension type for myself and using it as part of my custom link relation URIs. For example,

{
  "links": {
    "mycompany:avatar": "https://static.myapp.com/avatars/foo.png"
  }
}

I hope this isn’t too controversial, but I wanted to post it so that any fellow travelers following the same road I did will see what one weary API designer has done to reconcile the imperfections of messy APIs with the rectilinear world of specs.

3 Likes

I just read the start of your post, but I’m fairly sure you can place whichever links you want in a resource level links object: From here:

The optional links member within each resource object contains links related to the resource.

If present, this links object MAY contain a self link that identifies the resource represented by the resource object.

The way I read it, it does not say that the object may not contain any other links; it just specifies what the self link is.

We are frequently using custom resource links like your avatar.

1 Like

That’s what I thought, but links is an “object defined by this specification”, and as such, it “MUST NOT contain any additional members”. Comments on the linked github issue, and other issues, seem to support this. I’m not too happy with it (it’s not very clearly communicated in the spec), but I believe that my “workaround” is both principled and convenient :).

1 Like

I skimmed roughly through it, but didn’t find anything that said this - could you point me in the right direction?

Oh, it wasn’t in that github issue itself - it was in the comment here that led me to that issue! Links Object Clarification Needed

If we understand the spec correctly, this means “self” and “related” are
the only links we can specify.

Right now, yes, that’s correct.

I’m not sure if the spec has changed since then, but the issue linked to in that thread is still open. And there doesn’t seem to be any positive language in the current spec that suggests links objects are extendable with additional members. The spec does say this:

Note: Additional members may be specified for links objects and link objects in the future. It is also possible that the allowed values of additional members will be expanded (e.g. a collection link may support an array of values, whereas a self link does not).

This doesn’t suggest APIs are free to add their own links; but it does suggest you should take care if you do choose to do so, in case your custom links should conflict with future additions to the spec.

The 1.1 spec goes into much more detail about what “link objects” (distinct from the “links object”) may contain, but I still don’t see any language that counters the overarching

Unless otherwise noted, objects defined by this specification MUST NOT contain any additional members. Client and server implementations MUST ignore members not recognized by this specification.

I just stumpled upon the same issue. This actually makes the spec unusable for many hypermedia use cases. The links relation names are mostly created in the context of a domain specific API. So if I build a movie API it should be possible that a movie resource provides links to directors (here a relationship could be used). But a movie resource should also be able to provide a link to its IMDB page. With the current JSON:API spec, this would be not compliant with the spec as I understand it. The provided JSON Schema was not complaining about this, but I just checked the merge request for the upcoming 1.1 Schema, which actually would complain.

It could be implemented in a compliant way using an extension as described in the initial post on this thread. Using an extension does not only prevent naming conflicts with members, which may be added to the spec in previous versions. It also allows you to define additional semantics about these links.

As an alternative, having an URL as an attribute value is also valid. You could see a reference to IMDB or an avatar as an attribute of a resource.

The links object in JSON:API specification is meant to be used to discover related JSON:API documents. I feel using it for other purposes, such as linking to resources not being in JSON:API format, goes against the intend of the spec. Just because it is named links does not mean that all URLs must go into it!

Could the meta field alternatively be used for adding non-JSON:API links?

The meta structure of a resource or response can be used for anything you want. While the content is completely undefined by JSON:API, an application can provide it’s own documentation for what’s found there.

-Fred

Since 1.1 came out in September, the section of the specification that details this (JSON:API — Latest Specification (v1.1)) actually does seem to imply that you can use any relation type. It even references RFC8288!

The string MUST be a valid link relation type.

I think the only valid place to do this would be at the resource level, since the top-level links document explicitly says that only certain types are allowed there.

The reason I’m interested in this is because I want to be able to link to not-yet-JSON:API relations to add context and relevance to the resource (understanding that the automatic discoverability of such resources is subject to some limitations).

That’s a misunderstanding of the spec. You can specify a link by providing a link relation type as rel member of a link object. But the specification does not allow implementation-specific members in links object.

The specification forbids adding any implementation-specific members unless explicitly allowed:

Unless otherwise noted, objects defined by this specification or any applied extensions MUST NOT contain any additional members.

There is no explicit exception for links independing if being defined for document, resource object or relationship.

So links doesn’t mean “any old links” but only means that links that JSON:API approves of? Why is is this so limited? To be honest, this is really the one part of JSON:API that I find the most useful. I have no interest in using relationships because it is too limiting in terms of assuming that my underlying structures are implemented and managed using a relational database.

Can you go into more detail about how implementing an extension for this would be a practical solution? It just seems like a whole lot of bother for something that I should just be able to embed into an already existing links section.

I think it would be helpful if you elaborate on that one more. I don’t think that the way how JSON:API specification represents the relationship between resources assumes a relational database. It only assumes that those resources are also available through a JSON:API compliant API.

It only assumes that those resources are also available through a JSON:API compliant API.

That is actually the original problem. Not all resources that I want to link to are JSON:API compliant. I may be linking to existing resources which are well defined and established but are outside the bounds of my control (and aren’t JSON:API). There isn’t currently an acceptable place to put those links (I was going to put them in the resource’s links section, but you have since said that they can’t go there for some reason).

I have looked closer at the spec and now see that it is not required to expose URLs to the relationships themselves, which to me feels like a relational DB kind of activity. To your point, maybe that isn’t exclusively the case, as a graph DB also has relationship concepts. Do you know of any projects that have tried to use JSON:API with an underlying graph DB? I’d be curious to see how well the concepts align.