-
Notifications
You must be signed in to change notification settings - Fork 890
Description
I'd like to float a proposal that comes from considering points raised by @ahacking, @ethanresnick, @wvteijlingen, and others in #238, #262, #276, and other recent issues. It represents a breaking change that should be considered carefully before the spec hits 1.0.
The proposal is to make the format of the links
object identical in both the top-level and resource-level. Each link can be represented either by an object or a string. Link objects can contain two properties: href
and / or type
. If a link is represented with a string, it should be assumed to be href
.
The implications of this change are pretty drastic:
- It reduces processing rules for implementations by establishing a single rule for processing
links
. - IDs for has-one and has-many relationships will be represented alongside other attributes in a resource. (Of course, an implementation may instead choose to exclusively provide hrefs for relationships and never provide related IDs.)
- The term
links
will never appear in URL template expressions or in relationship URLs. - There is no need to call out a separate "collection object"
- It will be clear that resource-level links are redundant and used to override top-level links. I imagine that many APIs will exclusively use top-level links and never use resource-level links.
- It will be clear that expressions in URL templates can reference any attribute in a resource (even complex object attributes) in order to form links to another resource (see Cannot link from embedded objects #238).
- It will move JSON API one step closer to "standard REST APIs", which will probably increase adoption.
If there is adequate support for this change, I'll create a PR for detailed review.
Examples
Mixed top-level and resource-level links
Before:
{
"links": {
"posts.comments": "http://example.com/comments?post={posts}"
},
"posts": [{
"id": "1",
"title": "Rails is Omakase",
"links": {
"comments": {
"href": "http://example.com/comments?post=1"
}
}
}, {
"id": "2",
"title": "The Parley Letter",
"links": {
"comments": {
"href": "http://example.com/comments?post=2"
}
}
}]
}
After:
{
"links": {
"posts.comments": "http://example.com/comments?post={posts}"
},
"posts": [{
"id": "1",
"title": "Rails is Omakase",
"links": {
"comments": "http://example.com/comments?post=1"
}
}, {
"id": "2",
"title": "The Parley Letter",
"links": {
"comments": "http://example.com/comments?post=2"
}
}]
}
Top-level links, resource-level relationship IDs
Before:
{
"links": {
"posts.author": {
"href": "http://example.com/people/{posts.author}",
"type": "people"
},
"posts.comments": {
"href": "http://example.com/comments?post={posts}",
"type": "comments"
}
},
"posts": [{
"id": "1",
"title": "Rails is Omakase",
"links": {
"author": "9"
}
}, {
"id": "2",
"title": "The Parley Letter",
"links": {
"author": "9"
}
}]
}
After:
{
"links": {
"posts.author": {
"href": "http://example.com/people/{posts.author}",
"type": "people"
},
"posts.comments": {
"href": "http://example.com/comments?post={posts}",
"type": "comments"
}
},
"posts": [{
"id": "1",
"title": "Rails is Omakase",
"author": "9"
}, {
"id": "2",
"title": "The Parley Letter",
"author": "9"
}]
}
Resource-level links
Before:
{
"posts": [{
"id": "1",
"title": "Rails is Omakase",
"links": {
"author": {
"href": "http://example.com/people/9"
},
"comments": {
"href": "http://example.com/comments?post=1"
}
}
}, {
"id": "2",
"title": "The Parley Letter",
"links": {
"author": {
"href": "http://example.com/people/9"
},
"comments": {
"href": "http://example.com/comments?post=2"
}
}
}]
}
After:
{
"posts": [{
"id": "1",
"title": "Rails is Omakase",
"links": {
"author": "http://example.com/people/9",
"comments": "http://example.com/comments?post=1"
}
}, {
"id": "2",
"title": "The Parley Letter",
"links": {
"author": "http://example.com/people/9",
"comments": "http://example.com/comments?post=2"
}
}]
}
Compound documents
Before:
{
"links": {
"posts.author": {
"href": "http://example.com/people/{posts.author}",
"type": "people"
},
"posts.comments": {
"href": "http://example.com/comments/{posts.comments}",
"type": "comments"
}
},
"posts": [{
"id": "1",
"title": "Rails is Omakase",
"links": {
"author": "9",
"comments": [ "1", "2", "3" ]
}
}, {
"id": "2",
"title": "The Parley Letter",
"links": {
"author": "9",
"comments": [ "4", "5" ]
}
}, {
"id": "3",
"title": "Dependency Injection is Not a Virtue",
"links": {
"author": "9",
"comments": [ "6" ]
}
}],
"linked": {
"people": [{
"id": "9",
"name": "@d2h"
}],
"comments": [{
"id": "1",
"body": "Mmmmmakase"
}, {
"id": "2",
"body": "I prefer unagi"
}, {
"id": "3",
"body": "What's Omakase?"
}, {
"id": "4",
"body": "Parley is a discussion, especially one between enemies"
}, {
"id": "5",
"body": "The parsley letter"
}, {
"id": "6",
"body": "Dependency Injection is Not a Vice"
}]
}
}
After:
{
"links": {
"posts.author": {
"href": "http://example.com/people/{posts.author}",
"type": "people"
},
"posts.comments": {
"href": "http://example.com/comments/{posts.comments}",
"type": "comments"
}
},
"posts": [{
"id": "1",
"title": "Rails is Omakase",
"author": "9"
"comments": [ "1", "2", "3" ]
}, {
"id": "2",
"title": "The Parley Letter",
"author": "9",
"comments": [ "4", "5" ]
}, {
"id": "3",
"title": "Dependency Injection is Not a Virtue",
"author": "9",
"comments": [ "6" ]
}],
"linked": {
"people": [{
"id": "9",
"name": "@d2h"
}],
"comments": [{
"id": "1",
"body": "Mmmmmakase"
}, {
"id": "2",
"body": "I prefer unagi"
}, {
"id": "3",
"body": "What's Omakase?"
}, {
"id": "4",
"body": "Parley is a discussion, especially one between enemies"
}, {
"id": "5",
"body": "The parsley letter"
}, {
"id": "6",
"body": "Dependency Injection is Not a Vice"
}]
}
}
Complex attributes
Before:
{
"links": {
"items.manufacturer": {
"href": "http://example.com/manufacturers/{items.manufacturer}",
"type": "manufacturers"
},
"items.cost.currency": {
"href": "http://example.com/currencies/{items.cost.currency}",
"type": "currencies"
}
},
"items": {
"id": "1",
"description": "Portrait of Alexander Hamilton",
"cost": {
"amount": "10.00",
"currency": "usd"
},
"links": {
"manufacturer": "321"
}
}
}
After:
{
"links": {
"items.manufacturer": {
"href": "http://example.com/manufacturers/{items.manufacturer}",
"type": "manufacturers"
},
"items.cost.currency": {
"href": "http://example.com/currencies/{items.cost.currency}",
"type": "currencies"
}
},
"items": {
"id": "1",
"description": "Portrait of Alexander Hamilton",
"cost": {
"amount": "10.00",
"currency": "usd"
},
"manufacturer": "321"
}
}