I've noticed there is a non-standard behavior (feature?) that's breaking the json api spec when including different combinations of fields and include parameters.
For example, given three models Author, Book, and Article where an author has many books as well as many articles and both books and articles belong to an author:
if you request /authors/1 you will get a payload that looks like:
{
"data": {
"id": 1,
"type": "authors",
"attributes": {
"name": "Neal Stephenson"
},
"relationships": {
"books": {
"meta": {
"included": false
}
},
"articles": {
"meta": {
"included": false
}
}
}
}
The fact that "relationships" is present when I didn't request anything in the include param is a little weird, but I guess it's helpful? More on this in a bit...
Next hitting /authors/1?include=books outputs something like:
{
"data": {
"id": 1,
"type": "authors",
"attributes": {
"name": "Neal Stephenson"
},
"relationships": {
"books": {
"data": [
{
"type": "books",
"id": 1
},
{
"type": "books",
"id": 2
}
]
}
}
},
"included": [
{
"id": 1,
"type": "books",
"attributes": {
"name": "Snow Crash"
}
},
{
"id": 2,
"type": "books",
"attributes": {
"name": "Cryptonomicon"
}
}
]
}
At first glance that seems pretty much as expected, and actually in my opinion this is correct. But what's weird is that the relationships node no longer includes:
"articles": {
"meta": {
"included": false
}
}
meta is a non-standard, anything goes kinda field regardless, and relationships may or may not exist at any given time according to the spec so I guess this might be technically correct, but it's not what I would consider to be good consistency.
But it gets worse:
Go ahead and request /authors/1?include=books&fields[books]=name and the linkage between relationships and included is straight up broken:
{
"data": {
"id": 1,
"type": "authors",
"attributes": {
"name": "Neal Stephenson"
}
},
"included": [
{
"id": 1,
"type": "books",
"attributes": {
"name": "Snow Crash"
}
},
{
"id": 2,
"type": "books",
"attributes": {
"name": "Cryptonomicon"
}
}
]
}
You might say "Oh but you can just infer the books belong to the main resource," except the same thing happens on the index action as well (/authors?include=books&fields[books]=name):
{
"data": [
{
"id": 1,
"type": "authors",
"attributes": {
"name": "Neal Stephenson"
}
},
{
"id": 2,
"type": "authors",
"attributes": {
"name": "R.A. Salvatore"
}
}
],
"included": [
{
"id": 1,
"type": "books",
"attributes": {
"name": "Snow Crash"
}
},
{
"id": 2,
"type": "books",
"attributes": {
"name": "Cryptonomicon"
}
},
{
"id": 3,
"type": "books",
"attributes": {
"name": "The Legend of Drizzt"
}
}
]
}
That ain't gunna work for anybody.
Anyway, I've been able to break this using various combinations of include and fields params and it all seems to boil down to one culprit (which is a combination of two conflicting ideas):
- The attempt to be extra helpful with non-standard meta information in the
relationships section when no relationships were asked for
- This line passing
fields instead of include into the requested_relationships method.
A naive fix is to modify two methods in lib/jsonapi/serializable/resource.rb
rels = requested_relationships(fields).each_with_object({}) do |(k, v), h|
h[k] = v.as_jsonapi(include.include?(k))
end
to
rels = requested_relationships(include).each_with_object({}) do |(k, v), h|
h[k] = v.as_jsonapi(include.include?(k))
end
and
def requested_relationships(fields)
@_relationships.select { |k, _| fields.nil? || fields.include?(k) }
end
to
def requested_relationships(includes)
return {} if includes.empty?
@_relationships.select { |k, _| includes.include?(k) }
end
This has one caveat: relationships is no longer returned unless a user requested one or more via the include param (which in turn breaks a bunch of specs). However, this is still technically correct according to spec.
So what do you say? If I put in a PR to fix it can we live without
"relationships": {
"books": {
"meta": {
"included": false
}
},
"articles": {
"meta": {
"included": false
}
}
}
which really wasn't helping anyone anyway?
I've noticed there is a non-standard behavior (feature?) that's breaking the json api spec when including different combinations of
fieldsandincludeparameters.For example, given three models
Author,Book, andArticlewhere an author has many books as well as many articles and both books and articles belong to an author:if you request
/authors/1you will get a payload that looks like:{ "data": { "id": 1, "type": "authors", "attributes": { "name": "Neal Stephenson" }, "relationships": { "books": { "meta": { "included": false } }, "articles": { "meta": { "included": false } } } }The fact that
"relationships"is present when I didn't request anything in theincludeparam is a little weird, but I guess it's helpful? More on this in a bit...Next hitting
/authors/1?include=booksoutputs something like:{ "data": { "id": 1, "type": "authors", "attributes": { "name": "Neal Stephenson" }, "relationships": { "books": { "data": [ { "type": "books", "id": 1 }, { "type": "books", "id": 2 } ] } } }, "included": [ { "id": 1, "type": "books", "attributes": { "name": "Snow Crash" } }, { "id": 2, "type": "books", "attributes": { "name": "Cryptonomicon" } } ] }At first glance that seems pretty much as expected, and actually in my opinion this is correct. But what's weird is that the
relationshipsnode no longer includes:metais a non-standard, anything goes kinda field regardless, andrelationshipsmay or may not exist at any given time according to the spec so I guess this might be technically correct, but it's not what I would consider to be good consistency.But it gets worse:
Go ahead and request
/authors/1?include=books&fields[books]=nameand the linkage betweenrelationshipsandincludedis straight up broken:{ "data": { "id": 1, "type": "authors", "attributes": { "name": "Neal Stephenson" } }, "included": [ { "id": 1, "type": "books", "attributes": { "name": "Snow Crash" } }, { "id": 2, "type": "books", "attributes": { "name": "Cryptonomicon" } } ] }You might say "Oh but you can just infer the books belong to the main resource," except the same thing happens on the index action as well (
/authors?include=books&fields[books]=name):{ "data": [ { "id": 1, "type": "authors", "attributes": { "name": "Neal Stephenson" } }, { "id": 2, "type": "authors", "attributes": { "name": "R.A. Salvatore" } } ], "included": [ { "id": 1, "type": "books", "attributes": { "name": "Snow Crash" } }, { "id": 2, "type": "books", "attributes": { "name": "Cryptonomicon" } }, { "id": 3, "type": "books", "attributes": { "name": "The Legend of Drizzt" } } ] }That ain't gunna work for anybody.
Anyway, I've been able to break this using various combinations of
includeandfieldsparams and it all seems to boil down to one culprit (which is a combination of two conflicting ideas):relationshipssection when no relationships were asked forfieldsinstead ofincludeinto therequested_relationshipsmethod.A naive fix is to modify two methods in lib/jsonapi/serializable/resource.rb
to
and
to
This has one caveat:
relationshipsis no longer returned unless a user requested one or more via theincludeparam (which in turn breaks a bunch of specs). However, this is still technically correct according to spec.So what do you say? If I put in a PR to fix it can we live without
which really wasn't helping anyone anyway?