The jsonapi standard specifies the following about primary data:
Primary data MUST be either:
- a single resource object, a single resource identifier object, or null, for requests that target single resources
- an array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections
Expected behavior vs actual behavior
Given the jsonapi structured json copied from issue https://github.com/rails-api/active_model_serializers/issues/1484#issuecomment-207166790 (which is the example given at jsonapi.org):
{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
},
"links": {
"self": "http://example.com/articles/1"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
},
"comments": {
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": "http://example.com/articles/1/comments"
},
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
}
}]
}
The data should be deserialized into a ruby data object when passed to ActiveModelSerializers::Deserialization.jsonapi_parse!.
The result of passing the json to ActiveModelSerializers::Deserialization.jsonapi_parse! is an error:
ActiveModelSerializers::Adapter::JsonApi::Deserialization::InvalidDocument: Invalid payload ({:data=>"Expected hash"})
JSON.parse('stringified_json')ActiveModelSerializers::Deserialization.jsonapi_parse!(parsed_stringified_json)ActiveModelSerializers Version:
0.10.0.rc5
Integrated application and version:
Rails 4.2.5
I did a little bit of looking, and the error is thrown because of the line here: https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/adapter/json_api/deserialization.rb#L117
The validate_payload function will always fail if the primary data is anything other than a hash. Since jsonapi collections are represented in the primary data as an array, this will not correctly parse jsonapi formatted json.
The solution suggested in the original issue https://github.com/rails-api/active_model_serializers/issues/1484#issuecomment-179538677:
parsed_stringified_json["data"].map { |x| ActiveModelSerializers::Deserialization.jsonapi_parse!(x) }
also results in an error ActiveModelSerializers::Adapter::JsonApi::Deserialization::InvalidDocument: Invalid payload ({:data=>"Expected hash"})
Original issue: https://github.com/rails-api/active_model_serializers/issues/1484
Add data:, as a workaround.
parsed_stringified_json["data"].map { |x| ActiveModelSerializers::Deserialization.jsonapi_parse!(data: x) }
The idea behind deserialization when I wrote it was to deserialize payloads issued to the server, which, according to the spec, consist of singular data only. What is your use case for deserializing collections? (Just trying to get a better grasp of whether it should be added to AMS or not.)
@beauby Thanks for providing the workaround.
My particular use case arose while writing controller specs with RSpec; I want to be able to deserialize the responses from my servers to determine if they're serving up the right data based on current user permissions, query params, etc. Another potential use case that I thought of is server-to-server communications where a requesting server implementing AMS could deserialize the response from another server implementing AMS.
As mentioned, "deserialize" in the current AMS context means "jsonapi create/update payload to ActiveRecord-ready hash".
For your use case, you might be interested in a gem I am working on called jsonapi_parser (which I intend to be a toolbox for testing/validating various JSON API stuff).
@beauby Understood. Thanks for providing the jsonapi_parser gem. Do you think it would someday make sense to include deserialization of json api collections in AMS? If not, this ticket could probably be closed in favor of using jsonapi_parser.
I think deserializing collections, in the sense of "building a collection of objects from a JSON API document representing a collection" is beyond the scope of AMS, and I'd advise using some other tool for this (jsonapi_parser for instance).
Same issue:
ActiveModelSerializers::Adapter::JsonApi::Deserialization::InvalidDocument (Invalid payload ({:data=>"Expected hash"}): {"controller"=>"courts", "action"=>"create"}):
But solved after create initializer mime_types.rb:
api_mime_types = %w(
application/json
text/x-json
)
Mime::Type.unregister :json
Mime::Type.register 'application/vnd.api+json', :json, api_mime_types
Most helpful comment
I think deserializing collections, in the sense of "building a collection of objects from a JSON API document representing a collection" is beyond the scope of AMS, and I'd advise using some other tool for this (
jsonapi_parserfor instance).