From f943f43b19058abd832c4aa10b0b4143c4c43f40 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Wed, 29 Sep 2021 13:26:57 -0600 Subject: [PATCH 1/3] add support for :data arrays when deserializing --- lib/jsonapi/deserialization.rb | 26 ++- spec/deserialization_spec.rb | 319 ++++++++++++++++++++++++++------- 2 files changed, 282 insertions(+), 63 deletions(-) diff --git a/lib/jsonapi/deserialization.rb b/lib/jsonapi/deserialization.rb index 0a9d5de..d33f58d 100644 --- a/lib/jsonapi/deserialization.rb +++ b/lib/jsonapi/deserialization.rb @@ -49,9 +49,29 @@ def jsonapi_deserialize(document, options = {}) options[opt_name] = Array(opt_value).map(&:to_s) if opt_value end - relationships = primary_data['relationships'] || {} - parsed = primary_data['attributes'] || {} - parsed['id'] = primary_data['id'] if primary_data['id'] + is_array = primary_data.is_a?(Array) + if is_array + primary_data.map do |datum| + jsonapi_deserialize_single_data_item(datum, options) + end + else + jsonapi_deserialize_single_data_item(primary_data, options) + end + end + + # Returns a transformed dictionary following [ActiveRecord::Base] specs for + # a single data element + # + # @param [Hash] document + # @param [Hash] options + # only: Array of symbols of whitelisted fields. + # except: Array of symbols of blacklisted fields. + # polymorphic: Array of symbols of polymorphic fields. + # @return [Hash] + def jsonapi_deserialize_single_data_item(data_item, options = {}) + relationships = data_item['relationships'] || {} + parsed = data_item['attributes'] || {} + parsed['id'] = data_item['id'] if data_item['id'] # Remove unwanted items from a dictionary. if options['only'] diff --git a/spec/deserialization_spec.rb b/spec/deserialization_spec.rb index b23c183..f66e3f5 100644 --- a/spec/deserialization_spec.rb +++ b/spec/deserialization_spec.rb @@ -2,85 +2,284 @@ RSpec.describe JSONAPI::Deserialization do let(:jsonapi_deserialize) { UsersController.new.method(:jsonapi_deserialize) } - let(:document) do - { - data: { - id: 1, - type: 'note', - attributes: { - title: 'Title 1', - date: '2015-12-20' - }, - relationships: { - author: { - data: { - type: 'user', - id: 2 - } - }, - second_author: { - data: nil + + context 'for single resource' do + let(:document) do + { + data: { + id: 1, + type: 'note', + attributes: { + title: 'Title 1', + date: '2015-12-20' }, - notes: { - data: [ - { - type: 'note', - id: 3 - }, - { - type: 'note', - id: 4 + relationships: { + author: { + data: { + type: 'user', + id: 2 } - ] + }, + second_author: { + data: nil + }, + notes: { + data: [ + { + type: 'note', + id: 3 + }, + { + type: 'note', + id: 4 + } + ] + } } } } - } - end - - describe '#jsonapi_deserialize' do - it do - expect(jsonapi_deserialize.call(document)).to eq( - 'id' => 1, - 'date' => '2015-12-20', - 'title' => 'Title 1', - 'author_id' => 2, - 'second_author_id' => nil, - 'note_ids' => [3, 4] - ) - end - - context 'with `only`' do - it do - expect(jsonapi_deserialize.call(document, only: :notes)).to eq( - 'note_ids' => [3, 4] - ) - end end - context 'with `except`' do + describe '#jsonapi_deserialize' do it do - expect( - jsonapi_deserialize.call(document, except: [:date, :title]) - ).to eq( + expect(jsonapi_deserialize.call(document)).to eq( 'id' => 1, + 'date' => '2015-12-20', + 'title' => 'Title 1', 'author_id' => 2, 'second_author_id' => nil, 'note_ids' => [3, 4] ) end + + context 'with `only`' do + it do + expect(jsonapi_deserialize.call(document, only: :notes)).to eq( + 'note_ids' => [3, 4] + ) + end + end + + context 'with `except`' do + it do + expect( + jsonapi_deserialize.call(document, except: [:date, :title]) + ).to eq( + 'id' => 1, + 'author_id' => 2, + 'second_author_id' => nil, + 'note_ids' => [3, 4] + ) + end + end + + context 'with `polymorphic`' do + it do + expect( + jsonapi_deserialize.call( + document, only: :author, polymorphic: :author + ) + ).to eq( + 'author_id' => 2, + 'author_type' => User.name + ) + end + end end + end - context 'with `polymorphic`' do + context 'for many resources' do + let(:document) do + { + data: [ + { + id: 1, + type: 'note', + attributes: { + title: 'Title 1', + date: '2015-12-20' + }, + relationships: { + author: { + data: { + type: 'user', + id: 2 + } + }, + second_author: { + data: nil + }, + notes: { + data: [ + { + type: 'note', + id: 3 + }, + { + type: 'note', + id: 4 + } + ] + } + } + }, + { + id: 5, + type: 'note', + attributes: { + title: 'Title 2', + date: '2019-11-20' + }, + relationships: { + author: { + data: { + type: 'user', + id: 6 + } + }, + second_author: { + data: nil + }, + notes: { + data: [ + { + type: 'note', + id: 7 + }, + { + type: 'note', + id: 8 + } + ] + } + } + }, + { + id: 9, + type: 'note', + attributes: { + title: 'Title 3', + date: '2020-10-05' + }, + relationships: { + author: { + data: { + type: 'user', + id: 10 + } + }, + second_author: { + data: nil + }, + notes: { + data: [ + { + type: 'note', + id: 11 + }, + { + type: 'note', + id: 12 + } + ] + } + } + } + ] + } + end + + describe '#jsonapi_deserialize' do it do - expect( - jsonapi_deserialize.call( + expect(jsonapi_deserialize.call(document)).to match_array( + [ + { + 'id' => 1, + 'date' => '2015-12-20', + 'title' => 'Title 1', + 'author_id' => 2, + 'second_author_id' => nil, + 'note_ids' => [3, 4] + }, + { + 'id' => 5, + 'date' => '2019-11-20', + 'title' => 'Title 2', + 'author_id' => 6, + 'second_author_id' => nil, + 'note_ids' => [7, 8] + }, + { + 'id' => 9, + 'title' => 'Title 3', + 'date' => '2020-10-05', + 'author_id' => 10, + 'second_author_id' => nil, + 'note_ids' => [11, 12] + } + ] + ) + end + + context 'with `only`' do + it do + expect(jsonapi_deserialize.call(document, only: :notes)) + .to match_array( + [ + { + 'note_ids' => [3, 4] + }, + { + 'note_ids' => [7, 8] + }, + { + 'note_ids' => [11, 12] + } + ] + ) + end + end + + context 'with `except`' do + it do + expect(jsonapi_deserialize.call(document, except: [:date, :title])) + .to match_array( + [ + { + 'id' => 1, + 'author_id' => 2, + 'second_author_id' => nil, + 'note_ids' => [3, 4] + }, + { + 'id' => 5, + 'author_id' => 6, + 'second_author_id' => nil, + 'note_ids' => [7, 8] + }, + { + 'id' => 9, + 'author_id' => 10, + 'second_author_id' => nil, + 'note_ids' => [11, 12] + } + ] + ) + end + end + + context 'with `polymorphic`' do + it do + expect(jsonapi_deserialize.call( document, only: :author, polymorphic: :author + )).to match_array( + [ + { 'author_id' => 2, 'author_type' => 'User' }, + { 'author_id' => 6, 'author_type' => 'User' }, + { 'author_id' => 10, 'author_type' => 'User' } + ] ) - ).to eq( - 'author_id' => 2, - 'author_type' => User.name - ) + end end end end From 47e819c7010359eb81ad8ce28f3ea501b4e27083 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Wed, 29 Sep 2021 13:36:20 -0600 Subject: [PATCH 2/3] remove extraneous variable in new deserialization code --- lib/jsonapi/deserialization.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/jsonapi/deserialization.rb b/lib/jsonapi/deserialization.rb index d33f58d..d8cfc1e 100644 --- a/lib/jsonapi/deserialization.rb +++ b/lib/jsonapi/deserialization.rb @@ -49,8 +49,7 @@ def jsonapi_deserialize(document, options = {}) options[opt_name] = Array(opt_value).map(&:to_s) if opt_value end - is_array = primary_data.is_a?(Array) - if is_array + if primary_data.is_a?(Array) primary_data.map do |datum| jsonapi_deserialize_single_data_item(datum, options) end From b11b6d7ad39079655c22979e1bea349178e31d36 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Wed, 29 Sep 2021 13:41:41 -0600 Subject: [PATCH 3/3] improve naming of new deserialization collection support --- lib/jsonapi/deserialization.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/jsonapi/deserialization.rb b/lib/jsonapi/deserialization.rb index d8cfc1e..6f52584 100644 --- a/lib/jsonapi/deserialization.rb +++ b/lib/jsonapi/deserialization.rb @@ -51,26 +51,26 @@ def jsonapi_deserialize(document, options = {}) if primary_data.is_a?(Array) primary_data.map do |datum| - jsonapi_deserialize_single_data_item(datum, options) + jsonapi_deserialize_data_element(datum, options) end else - jsonapi_deserialize_single_data_item(primary_data, options) + jsonapi_deserialize_data_element(primary_data, options) end end # Returns a transformed dictionary following [ActiveRecord::Base] specs for # a single data element # - # @param [Hash] document + # @param [Hash] data_element # @param [Hash] options # only: Array of symbols of whitelisted fields. # except: Array of symbols of blacklisted fields. # polymorphic: Array of symbols of polymorphic fields. # @return [Hash] - def jsonapi_deserialize_single_data_item(data_item, options = {}) - relationships = data_item['relationships'] || {} - parsed = data_item['attributes'] || {} - parsed['id'] = data_item['id'] if data_item['id'] + def jsonapi_deserialize_data_element(data_element, options = {}) + relationships = data_element['relationships'] || {} + parsed = data_element['attributes'] || {} + parsed['id'] = data_element['id'] if data_element['id'] # Remove unwanted items from a dictionary. if options['only']