-
-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JSON:API support #109
Comments
Hi @gabesullice , I think JSON:API support would be really great! I didn't realize they had links front and center as much as they did, so it seems like an almost obvious fit. |
The part that will be harder to express, is things inside the |
For example, if you had a resource If you had a resource Other sibling keys of The most interesting of the sibling keys is I think for Ketting, you're right, the most important thing would be how to handle this structure elegantly:
How would one "follow" these links given that they're contextualized by their location in the response body? One idea I had a second argument to I think this fits well with the web linking spec, given it's language about "context IRIs", even though I don't think it was intended to be used this way. |
Practically, that might look like this:
|
What will be nice for JSON:API users is that they can completely ignore included items and not care if they are there or not. If they are, the cache will be prepopulated with them. I'm going a little bit through the specification, and noticed that Would something like this be possible
Then your example might look like: const resource = ketting.getResource('/posts');
const posts = await resource.followAll('json-api-resource');
for(const post of posts) {
console.log(await post.get());
} |
👍
Yesss. That's awesome.
I think for the vast majority of standard cases, this would be fine. But I don't think it would be an assumption that would hold up in all cases. Just recently, I've been implementing a way to get versioned objects from a JSON:API server. It's possible to end up with something like this:
At a later point, if you refreshed, the data might have a query string of |
I don't think that would not be possible. But I don't know that I really love the idea either. I think I'm having a knee-jerk reaction to the "magic" of it. It also feels like a case where the public API is being driven by the internal implementation of the client rather than the most intuitive thing for the user (which is not to say that my suggestion is the most intuitive either). |
Totally unrelated to this issue, but I'm not sure where else to put it... I just shamelessly stalked your Github and blog (😅) and came across your post about http2 and APIs and after reading it, I bet you might be interested in this little experiment of mine just for fun: https://github.com/gabesullice/hades. |
That's super interesting. I've been running around an idea for a last couple of weeks to try and write a RFC-style standard for something likeyour I get the hesitance against the Another option might be to treat |
What I'm really also saying is that it's kind of unfortunate that JSON:API didn't model collection resources as their own resource + a bunch of |
Please let me know if you start on it, I'd love to contribute!
Totally agree on the last point. In our server implementation, we're thinking about doing something in the same spirit for Honestly, JSON:API has a... complicated relationship with good hypermedia practices. I'm sure there will be more things to consider, even if this gets solved. I just went to read 8288§3.2 Link Context, which linked me to RFC3986§5 Reference Resolution. I admit I haven't really completely read/grokked it, but it looks like it has some related concepts and might be important for Ketting in other ways. Link headers look to have been designed to work with hierarchical/nested data since they can be anchored to resource fragments (The A last point about JSON:API linking wonkiness before I sign off for the day: There are yet more link sub-contexts within a resource object:
In that case, I think yet another implicit relation type would be needed, but it wouldn't be as simple as just using |
Exciting discussion here! I think it'd be interesting to get JSON:API spec maintainers @dgeb and @ethanresnick to chime in here :) |
Your explanation makes a lot of sense. I wasn't aware of I left some comments and thoughts on your PR (#111). |
As a JSON:API editor (who's thought a lot about hypermedia), I'm happy to weigh in. I hardly have the full context around ketting and the various available options, but I can say a bit about hypermedia in JSON:API.
This is an understatement :) But hopefully JSON:API's model will get simpler at some point, and we do know know about most of the issues raised in this thread (see e.g. json-api/json-api#898, json-api/json-api#834, and json-api/json-api#913). For now, here's how I would probably model JSON:API documents as collections of RFC 8288 links:
So, taking (a simplified version of) the example document from JSON:API's homepage: {
"links": {
"self": "http://example.com/articles",
"next": "http://example.com/articles?page[offset]=1"
},
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
}
},
"links": {
"self": "http://example.com/articles/1"
}
}],
"included": [{
"type": "people",
"id": "9",
"attributes": {
"firstName": "Dan",
"lastName": "Gebhardt",
"twitter": "dgeb"
},
"links": {
"self": "http://example.com/people/9"
}
}]
} You'd end up with links: Note: there are no item + collection links for the In terms of cached representations, you'd end up with:
|
@ethanresnick , thanks for this explanation. @gabesullice, does that alter your perspective on this? My take is that only What's not clear to me yet is, if other links blocks appear in the response body and they do have a |
Sure :) To your question:
Are you talking about the implicit
Also/unrelated, I realized shortly after posting that the method I gave for extracting the representations of the "sub-resources" is actually a little broken. When the "subresource" is a relationship object like you'd find at {
"data": {
"type": "people",
"id": "9",
"attributes": { /* ... */ },
"links": {
"self": "http://example.com/people/9"
}
}
} I would also probably repeat {
// simply a copy of data.links
"links": {
"self": "http://example.com/people/9"
},
"data": {
"type": "people",
"id": "9",
"attributes": { /* ... */ },
"links": {
"self": "http://example.com/people/9"
}
}
} Sorry about that omission. The one final caveat about extracting "sub resource" representations in this way is that, with the introduction of profiles in 1.1, there are now top-level links that implicitly apply to every sub-resource too, namely {
"links": {
"profile": [/* links from top-level links.profile in http://example.com/articles */],
// other copied links from data.links
},
"data": { /* same contents as `data` in example above */ }
} Cascading these profile links certainly isn't hard, but it's a bit annoying that you'd have to add a special case for it (since cascading top-level links from the containing resource in the general case certainly is not safe). It also wouldn't be very future proof: if a new top-level link was added later that also implicitly applied to subresources, existing code would miss it. A much better approach imo would be for the JSON:API spec to allow the sub resources in the original response to simply repeat the top-level links that apply to them. So, a response for {
"links": {
"self": "http://example.com/articles",
"next": "http://example.com/articles?page[offset]=1",
"profile": ["http://example.com/some-profile"]
},
"data": [{
"type": "articles",
"id": "1",
"attributes": { /* ... */ },
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
// profile repeated here
"profile": ["http://example.com/some-profile"]
},
"data": { "type": "people", "id": "9" }
}
},
"links": {
"self": "http://example.com/articles/1"
// profile repeated here too
"profile": ["http://example.com/some-profile"]
}
}]
} Then, the subresource extraction logic can be simpler and relatively future proof. To summarize, it would go like this:
So, the representation for {
// this is just a full copy of data.links; no special-casing required.
"links": {
"self": "http://example.com/articles/1"
"profile": ["http://example.com/some-profile"]
},
data: {
"type": "articles",
"id": "1",
"attributes": { /* ... */ },
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author",
"profile": ["http://example.com/some-profile"]
},
"data": { "type": "people", "id": "9" }
}
},
"links": {
"self": "http://example.com/articles/1"
"profile": ["http://example.com/some-profile"]
}
} That works pretty nicely imo. To make it happen, though, you (or @gabesullice) would have to open an issue/PR on the JSON:API repo allowing the repetition of the |
Hi @ethanresnick , When I originally read your comment I read the whole thing. But when I looked at it yesterday I did a bit of a poor job with skimming it. All your answers make perfect sense, and so does the I feel with this in hand there's already a lot of stuff I can implement. Curious what @gabesullice thinks |
What we're trying to work out is: can we programmatically map a resource object to a resource on the client, and if so, how? FWIW, we're certainly not the first to encounter this JSON:API stumbling block. @steveklabnik summed up the distinction between a resource and a resource object (a.k.a "entity") nicely and I think that's where the confusion is coming from here. @ethanresnick addressed a lot of my concern about treating resource objects in a collection as an independent resource. I.e., that you need to "wrap" it in the JSON:API @evert, I haven't looked at you PR yet (I'll do that next), but I think you're primarily concerned with this because you want to pre-populate a cache of the target entities to avoid an additional request when "following" items? Honestly, I don't think that needs to happen (at least not soon). Following top-level links alone is fine to begin with.
I think a JSON:API profile would be a great way to establish a fragment format ;) *I don't really agree that type-id is a good scheme (because of relationship objects and the difficulty of parsing them out), but we can have that discussion elsewhere! I think using the |
There's a few different bits here that all are somewhat related.
parent.follow('item').follow('some-other-rel'); This works without caching the caching bit, but without caching it does imply that, in order to get the This is not a deal-breaker for me, but I imagine someone using this library with JSON:API might be surprised that there are more HTTP requests than needed. So ultimately, if there is a sensible JSON:API way to (as you say) map a Resource Object to a real Resource, you get a lot for free. |
I think the rule should be: if a resource object under a To start, I would issue an actual request in order to follow that link. I would not emulate it until the mapping rules are more fully understood. Edit: Also, I would not bother with |
So the bits i have implemented now are links for top-level objects and treating collection-members as 'item' links (if they have a self link). Is there more that can be done today? I'm thinking it might be possible to parse |
Although if someone is interested build a generic HATEAOS api browser based on Ketting it might be a nice little bonus. |
+1 to this concern. and to maybe holding off on trying to synthesize representations until the rules are better understood. Besides that, I'm excited to the see the progress here with the latest PR! |
Thanks all for the contributions. I'm closing this ticket for now. Hopefully any future gaps can be closed through an ext iteration of JSON:API. Happy NY! |
This is a really interesting client. I love it!
Are there any plans to add support JSON:API? If so, would you consider it (or a PR that does)?
The text was updated successfully, but these errors were encountered: