What is object serializer?
Object serializer is a tool for us to convert objects, namely objects in rails, into JSON format to serve HTTP responses that return JSON data. In rails, to implement serialize objects, we can use gems like fast_jsonapi
, active_model_serializers
or can use the Active Model Serializer
(AMS) provided by rails. But within the framework of this article, I will introduce to you gem fast_jsonapi
okay
Introducing the Fast JSON API gem
Fast JSON API
provides almost every function that the Active Model Serializer
provides. However, the advantage of Fast JSON API
lies in speed and performance, even up to 25 times in time compared to Active Model Serializer
. We can see through the following example:
1 2 3 4 | $ rspec <span class="token constant">Active</span> <span class="token constant">Model</span> <span class="token constant">Serializer</span> serialized <span class="token number">250</span> records <span class="token keyword">in</span> <span class="token number">138.71</span> ms <span class="token constant">Fast</span> <span class="token constant">JSON</span> <span class="token constant">API</span> serialized <span class="token number">250</span> records <span class="token keyword">in</span> <span class="token number">3.01</span> ms |
Through the above example, we can see how Fast JSON API
superior
Features of Fast JSON API
Basically, Fast JSON API will provide us with the following main functions:
- The declaration syntax is similar to
Active Model Serializer
- Supports relations
belongs_to
,has_many
,has_one
- Support for compound documents
- Caching
Setting
To install the Fast JSON API, we add this line to the gem file and then run bundle install
:
1 2 | gem <span class="token string">'fast_jsonapi'</span> |
After installation is complete, we can use the features of the Fast JSON API Let’s find out!
Use
We can use the bundled generator
command
1 2 | rails g serializer Movie name year |
This command will create in your project a serializer located in app/serializers/movie_serializer.rb
Model Definition
1 2 3 4 | <span class="token keyword">class</span> <span class="token class-name">Movie</span> attr_accessor <span class="token symbol">:id</span> <span class="token punctuation">,</span> <span class="token symbol">:name</span> <span class="token punctuation">,</span> <span class="token symbol">:year</span> <span class="token punctuation">,</span> <span class="token symbol">:actor_ids</span> <span class="token punctuation">,</span> <span class="token symbol">:owner_id</span> <span class="token punctuation">,</span> <span class="token symbol">:movie_type_id</span> <span class="token keyword">end</span> |
Serializer definition
This is the most important part. We will define how an object will be serialized:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">class</span> <span class="token class-name">MovieSerializer</span> include <span class="token constant">FastJsonapi</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ObjectSerializer</span> set_type <span class="token symbol">:movie</span> <span class="token comment"># optional</span> set_id <span class="token symbol">:owner_id</span> <span class="token comment"># optional</span> attributes <span class="token symbol">:name</span> <span class="token punctuation">,</span> <span class="token symbol">:year</span> has_many <span class="token symbol">:actors</span> belongs_to <span class="token symbol">:owner</span> <span class="token punctuation">,</span> record_type <span class="token punctuation">:</span> <span class="token symbol">:user</span> belongs_to <span class="token symbol">:movie_type</span> <span class="token keyword">end</span> |
Let’s see the results through the following example
We will create an object of the Movie
class as follows:
1 2 3 4 5 6 7 8 | movie <span class="token operator">=</span> <span class="token constant">Movie</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token class-name">movie <span class="token punctuation">.</span> id</span> <span class="token operator">=</span> <span class="token number">232</span> movie <span class="token punctuation">.</span> name <span class="token operator">=</span> <span class="token string">'test movie'</span> movie <span class="token punctuation">.</span> actor_ids <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token number">1</span> <span class="token punctuation">,</span> <span class="token number">2</span> <span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">]</span> movie <span class="token punctuation">.</span> owner_id <span class="token operator">=</span> <span class="token number">3</span> movie <span class="token punctuation">.</span> movie_type_id <span class="token operator">=</span> <span class="token number">1</span> movie |
We will then serialize this object:
Return hash
1 2 | hash <span class="token operator">=</span> <span class="token constant">MovieSerializer</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> movie <span class="token punctuation">)</span> <span class="token punctuation">.</span> serializable_hash |
Return JSON
1 2 | json_string <span class="token operator">=</span> <span class="token constant">MovieSerializer</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> movie <span class="token punctuation">)</span> <span class="token punctuation">.</span> serialized_json |
The result will produce the following output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <span class="token punctuation">{</span> <span class="token property">"data"</span> <span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"id"</span> <span class="token operator">:</span> <span class="token string">"3"</span> <span class="token punctuation">,</span> <span class="token property">"type"</span> <span class="token operator">:</span> <span class="token string">"movie"</span> <span class="token punctuation">,</span> <span class="token property">"attributes"</span> <span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"name"</span> <span class="token operator">:</span> <span class="token string">"Superman!"</span> <span class="token punctuation">,</span> <span class="token property">"year"</span> <span class="token operator">:</span> <span class="token null">null</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token property">"relationships"</span> <span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"actors"</span> <span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"data"</span> <span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"id"</span> <span class="token operator">:</span> <span class="token string">"1"</span> <span class="token punctuation">,</span> <span class="token property">"type"</span> <span class="token operator">:</span> <span class="token string">"actor"</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"id"</span> <span class="token operator">:</span> <span class="token string">"2"</span> <span class="token punctuation">,</span> <span class="token property">"type"</span> <span class="token operator">:</span> <span class="token string">"actor"</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token property">"owner"</span> <span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"data"</span> <span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"id"</span> <span class="token operator">:</span> <span class="token string">"3"</span> <span class="token punctuation">,</span> <span class="token property">"type"</span> <span class="token operator">:</span> <span class="token string">"user"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Attributes
Attributes are defined in Fast JSON API via method attributes
. This method also has an alias as an attribute
, which will help our code more clearly when defining a single attribute.
By default, the attributes of the object serializer are defined by taking attributes of the same name from the object in the Model. For example, the Movie class above, attributes such as name
, year
, .. will also be transferred directly to MovieSerializer
:
1 2 3 4 5 6 | <span class="token keyword">class</span> <span class="token class-name">MovieSerializer</span> include <span class="token constant">FastJsonapi</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ObjectSerializer</span> attribute <span class="token symbol">:name</span> <span class="token punctuation">,</span> <span class="token symbol">:year</span> <span class="token keyword">end</span> |
With the Fast JSON API
, we can define our own attributes besides the object’s existing attributes as follows:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">class</span> <span class="token class-name">MovieSerializer</span> include <span class="token constant">FastJsonapi</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ObjectSerializer</span> attributes <span class="token symbol">:name</span> <span class="token punctuation">,</span> <span class="token symbol">:year</span> attribute <span class="token symbol">:name_with_year</span> <span class="token keyword">do</span> <span class="token operator">|</span> object <span class="token operator">|</span> <span class="token string">" <span class="token interpolation"><span class="token delimiter tag">#{</span> object <span class="token punctuation">.</span> name <span class="token delimiter tag">}</span></span> ( <span class="token interpolation"><span class="token delimiter tag">#{</span> object <span class="token punctuation">.</span> year <span class="token delimiter tag">}</span></span> )"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
By implementing additional attribute :name_with_year
, object serializer will have additional attribute :name_with_year
as we have defined above.
In addition, we can override the default attributes (attributes taken directly from the Model object):
1 2 3 4 5 6 7 8 9 | <span class="token keyword">class</span> <span class="token class-name">MovieSerializer</span> include <span class="token constant">FastJsonapi</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ObjectSerializer</span> attribute <span class="token symbol">:name</span> <span class="token keyword">do</span> <span class="token operator">|</span> object <span class="token operator">|</span> <span class="token string">" <span class="token interpolation"><span class="token delimiter tag">#{</span> object <span class="token punctuation">.</span> name <span class="token delimiter tag">}</span></span> Part 2"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Attributes can be used under a different name by passing the original method or accessor with the shortcut proc
:
1 2 3 4 5 6 7 8 | <span class="token keyword">class</span> <span class="token class-name">MovieSerializer</span> include <span class="token constant">FastJsonapi</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ObjectSerializer</span> attributes <span class="token symbol">:name</span> attribute <span class="token symbol">:released_in_year</span> <span class="token punctuation">,</span> <span class="token operator">&</span> <span class="token symbol">:year</span> <span class="token keyword">end</span> |
Collection serialization
Besides serializing an independent object like the examples we have shown above, we can also serialize a collection of objects. The declaration syntax is quite similar:
1 2 3 | hash <span class="token operator">=</span> <span class="token constant">MovieSerializer</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> movie <span class="token punctuation">,</span> movie <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> serializable_hash json_string <span class="token operator">=</span> <span class="token constant">MovieSerializer</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> movie <span class="token punctuation">,</span> movie <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> serialized_json |
We can pass an is_collection
option to more clearly manage the serialization of an individual collection or single object. By default, if we do not pass this option, the Fast JSON API
will understand and distinguish when it is passed into a collection and when it is passed to a single object. However, Fast JSON API
is not always able to understand and distinguish as we wish. That’s why the Fast JSON API
has given us options for managing serialize collections or objects more easily:
1 2 3 4 | options <span class="token punctuation">[</span> <span class="token symbol">:is_collection</span> <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">nil</span> <span class="token comment"># or true or false</span> hash <span class="token operator">=</span> <span class="token constant">MovieSerializer</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> movie <span class="token punctuation">,</span> movie <span class="token punctuation">]</span> <span class="token punctuation">,</span> options <span class="token punctuation">)</span> <span class="token punctuation">.</span> serializable_hash json_string <span class="token operator">=</span> <span class="token constant">MovieSerializer</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> movie <span class="token punctuation">,</span> movie <span class="token punctuation">]</span> <span class="token punctuation">,</span> options <span class="token punctuation">)</span> <span class="token punctuation">.</span> serialized_json |
The is_colletion
options are:
nil
or not: TheFast JSON API
will automatically understand if the resource is a collection or a single object (However, there will be some limitations as I wrote above).true
: will always treat the resource passed as a collectionfalse
: will always treat the resource passed as a single object
Params
In some cases, the serializer object attributes can carry more information than the object attributes in the model. Passing params will help us handle different cases more easily. It’s a bit abstract and confusing, let’s take a look at the following example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token keyword">class</span> <span class="token class-name">MovieSerializer</span> include <span class="token constant">FastJsonapi</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ObjectSerializer</span> attributes <span class="token symbol">:name</span> <span class="token punctuation">,</span> <span class="token symbol">:year</span> attribute <span class="token symbol">:can_view_early</span> <span class="token keyword">do</span> <span class="token operator">|</span> movie <span class="token punctuation">,</span> params <span class="token operator">|</span> <span class="token comment"># in here, params is a hash containing the `:current_user` key</span> params <span class="token punctuation">[</span> <span class="token symbol">:current_user</span> <span class="token punctuation">]</span> <span class="token punctuation">.</span> is_employee <span class="token operator">?</span> <span class="token operator">?</span> <span class="token keyword">true</span> <span class="token punctuation">:</span> <span class="token keyword">false</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token comment"># ...</span> current_user <span class="token operator">=</span> <span class="token constant">User</span> <span class="token punctuation">.</span> <span class="token function">find</span> <span class="token punctuation">(</span> cookies <span class="token punctuation">[</span> <span class="token symbol">:current_user_id</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> serializer <span class="token operator">=</span> <span class="token constant">MovieSerializer</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> movie <span class="token punctuation">,</span> <span class="token punctuation">{</span> params <span class="token punctuation">:</span> <span class="token punctuation">{</span> current_user <span class="token punctuation">:</span> current_user <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> serializer <span class="token punctuation">.</span> serializable_hash |
In the above example, we will pass params[:current_user]
into the attribute :can_view_early
, this attribute will return true if params[:current_user].is_employee?
is true, and returns false otherwise. That’s why the value of the can_view_early
attribute will depend on the params we passed.
Conditional Relationship
Conditional Relationship can be defined by passing a Proc via the if:
keyword. Relationship will be serialized if Proc returns true
and vice versa, will not be serialized if Proc returns false
, similarly we can pass in both the record
the params
. Let’s take a look at the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">class</span> <span class="token class-name">MovieSerializer</span> include <span class="token constant">FastJsonapi</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ObjectSerializer</span> <span class="token comment"># Actors will only be serialized if the record has any associated actors</span> has_many <span class="token symbol">:actors</span> <span class="token punctuation">,</span> <span class="token keyword">if</span> <span class="token punctuation">:</span> <span class="token builtin">Proc</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">{</span> <span class="token operator">|</span> record <span class="token operator">|</span> record <span class="token punctuation">.</span> actors <span class="token punctuation">.</span> any <span class="token operator">?</span> <span class="token punctuation">}</span> <span class="token comment"># Owner will only be serialized if the :admin key of params is true</span> belongs_to <span class="token symbol">:owner</span> <span class="token punctuation">,</span> <span class="token keyword">if</span> <span class="token punctuation">:</span> <span class="token builtin">Proc</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">{</span> <span class="token operator">|</span> record <span class="token punctuation">,</span> params <span class="token operator">|</span> params <span class="token operator">&&</span> params <span class="token punctuation">[</span> <span class="token symbol">:admin</span> <span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token keyword">true</span> <span class="token punctuation">}</span> <span class="token keyword">end</span> <span class="token comment"># ...</span> current_user <span class="token operator">=</span> <span class="token constant">User</span> <span class="token punctuation">.</span> <span class="token function">find</span> <span class="token punctuation">(</span> cookies <span class="token punctuation">[</span> <span class="token symbol">:current_user_id</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> serializer <span class="token operator">=</span> <span class="token constant">MovieSerializer</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> movie <span class="token punctuation">,</span> <span class="token punctuation">{</span> params <span class="token punctuation">:</span> <span class="token punctuation">{</span> admin <span class="token punctuation">:</span> current_user <span class="token punctuation">.</span> admin <span class="token operator">?</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> serializer <span class="token punctuation">.</span> serializable_hash |
Epilogue
Above, I have shared the most basic knowledge enough that we can apply the Fast JSON API in our project, to learn more, you can check out the reference link to below. Good luck! https://github.com/Netflix/fast_jsonapi