Back to all posts

JSON Serialization in Rails: A Complete Guide

Posted by Paweł Dąbrowski on June 15, 2018

Since JavaScript has become the main language of the web and frontend frameworks are based on JavaScript, JSON serialization has become a very important part of many web apps. In this article, I’m going to explain what JSON serialization is, why you may need it in your Rails application and how to leverage existing libraries to implement JSON serialization in a concise and performant way. I will start with a high-level explanation, then discuss some of the most most popular solutions and finally model how to implement JSON serialization using the most optimal solution in a Ruby on Rails application.

Basic Ruby knowledge will be helpful  to fully understand the code presented in the tutorial.

What is JSON serialization?

JSON (JavaScript Object Notation) is a data format that encodes objects in a string. Such data representation can easily be translated between server and browser but also between server and server. Serialization is a process that transforms an object into that string.

Why you need JSON serialization in a Rails application

If your Rails application presents an API that utilizes JSON, it can be used with popular Javascript frameworks as well as any other application that can handle JSON.

The two stages of JSON serialization

The JSON serialization process consists of two stages: data preparation and transformation to the JSON format.

Data preparation

Data preparation consists of transforming Ruby objects into a hash map. If you have a Person model and each person has email and name attributes, then data ready for serialization would look like the following:

{"email" => "john@doe.com", "name" => "John Doe"}

Notice that in Rails you can call the #to_json method on a model instance. It will return a hash map with all model attributes. In most cases, you won’t need all model attributes so it’s important to prepare the hash map with the data you really need. Such behavior is a part of data preparation best practices.

Here is the full list of best practices that you should stick to when preparing data in order to have good performance:

  • Choose only those attributes you really need in the JSON response. The easiest way to do this in Rails is to call the #to_json method on a model instance and pass only :option which contains the array of attributes names you really need: person.to_json(only: [:email, :name])
  • Consider truncating long content. In the case that you need to display many records in a list, you may truncate descriptions or values to fit more records on the page. This truncation logic is best done on the backend, so that the frontend is only responsible for displaying the information.
  • Ensure attribute names are descriptive. If you are working on an existing app and you have to deal with poorly named attributes, take care to use more descriptive names  in the JSON response so it’s obvious on the frontend what a given field contains by only looking at its name.
  • Choose included associations wisely. Think about the attributes you really need when including associations. Loading unnecessary associations data is an easyway to slow down your data preparation process.

The most popular data preparation tools

There are various Ruby gems available for you to implement the JSON serialization in your Ruby on Rails app. Their APIs are designed in a similar way; however, there are some important differences. I’ll go through some of the most popular data preparation tools and compare metrics to determine the best data preparation tools solution.

For testing purposes, I will be using two models: Post and Comment.

How to follow along

If you want to test the code examples below on your side, you can generate a new Rails project and the models by using the following code:

rails new jsontest
cd jsontest
bundle exec rake db:create
bundle exec rails g model post title:string content:text published:boolean
bundle exec rails g model comment author:string body:text post_id:integer
bundle exec rake db:migrate

# app/models/post.rb

class Post < ActiveRecord::Base
  has_many :comments
end

# app/models/comment.rb

class Comment < ActiveRecord::Base
  belongs_to :post
end

# Let's load the test data - bundle exec rails c

post = Post.create!(title: "Post", content: "content", published: true)
Comment.create!(post: post, author: "Author", body: "Comment")

Active Model Serializers

The ActiveModel::Serializer implementation is very popular despite the fact that the master is not yet released. The current stable version is 0.10 and it is undergoing some renovations.

The gem comes with the handy generator that will create given serializer for us. Let’s start with adding the gem into our Gemfile:

gem 'active_model_serializers', '~> 0.10.0'

Now let’s generate the serializers for our Post and Comment model:

rails g serializer post
rails g serializer comment

Our serializers are generated in the app/serializers directory. 

Now, ensure that your serializers have the following contents:

# app/serializers/post_serializer.rb

class PostSerializer < ActiveModel::Serializer
  has_many :comments

  attributes :id, :title, :content
end

# app/serializers/comment_serializer.rb

class CommentSerializer < ActiveModel::Serializer
  attributes :id, :body, :author
end

We are ready to test our serializers:

post = Post.joins(:comments).first
PostSerializer.new(post).as_json # => {:id=>12, :title=>"Post", :content=>"content", :comments=>[{:id=>201, :body=>"Comment", :author=>"Author"}]}

JSONAPI-RB

The jsonapi-rb is an intuitive Ruby library, comprised of 4 independent micro-libraries: jsonapi-parser, jsonapi-renderer, jsonapi-serializable and jsonapi-deserializable.

Let’s add the gem to our Gemfile:

gem 'jsonapi-rails'

Now, we have to update our serializers stored under app/serializers/post_serializer.rb and app/serializers/comment_serializer.rb paths:

# app/serializers/post_serializer.rb

class PostSerializer < JSONAPI::Serializable::Resource
  type 'posts'
  has_many :comments
  attributes :id, :title, :content
end

# app/serializers/comment_serializer.rb

class CommentSerializer < JSONAPI::Serializable::Resource
  type 'comments'

  attributes :id, :author, :body
end

We are ready to test our serializers:

post = Post.joins(:comments).first
renderer = JSONAPI::Serializable::Renderer.new
renderer.render(post, class: { Post: PostSerializer, Comment: CommentSerializer }, include: [:comments]) # => {:data=>{:id=>"113", :type=>:posts, :attributes=>{:id=>113,
:title=>"Post",
:content=>"content"},
:relationships=>{:comments=>{:data=>[{:type=>:comments, 
:id=>"2702"}]}}}, :included=>[{:id=>"2702", :type=>:comments, 
:attributes=>{:id=>2702, :author=>"Author", :body=>"Comment"}}]}

Fast JSON API

The lib is a lightning fast JSON:API serializer for Ruby Objects created by Netflix.

Let’s add the gem to our Gemfile:

gem 'fast_jsonapi'

The next step is to update our serializers to use Fast JSON API gem:

# app/serializers/post_serializer.rb

class PostSerializer
  include FastJsonapi::ObjectSerializer
  attributes :title, :content
  has_many :comments
end

# app/serializers/comment_serializer.rb

class CommentSerializer
  include FastJsonapi::ObjectSerializer
  attributes :id, :body, :author
end

Now, we can prepare our data for serialization:

post = Post.joins(:comments).first
PostSerializer.new(post, include: [:comments]).serializable_hash # => 
{:data=>{:id=>"12", :type=>:post, :attributes=>{:title=>"Post", 
:content=>"content"}, :relationships=>{:comments=>{:data=>[{:id=>"201", :type=>:comment}]}}}, :included=>[{:id=>"201", :type=>:comment, 
:attributes=>{:id=>201, :body=>"Comment", :author=>"Author"}}]}

RABL

RABL (Ruby API Builder Language) is a Ruby templating system for generating JSON.

As usual, the first step is to add the gem in our Gemfile:

gem 'rabl'

This time we will not define serializers under app/serializers path but instead create the JSON templates. Create new files: app/views/post.rabl and app/views/comment.rabl:

# app/views/post.rabl

object @job

attributes :id, :title, :content

child :comments do
  extends "comment"
end

# app/views/comment.rabl

object @comment

attributes :id, :author, :body

By default, the gem automatically uses created templates in the Rails controllers when the response in the JSON format is requested. However, we can also prepare data from the console level:

post = Post.joins(:comments).first
Rabl.render(post, 'post', :view_path => 'app/views', :format => :hash) # 
=> {:id=>12, :title=>"Post", :content=>"content", :comments=>[{:id=>201, :author=>"Author", :body=>"Comment"}]}

JSON API Resources

The jsonapi-resourceslibrary is a resource-focused Rails library for developing JSON API compliant servers. It has its own website and documentation - http://jsonapi-resources.com/- which is a big advantage and helps to better understand concepts behind this architecture.

Let’s add the gem to our Gemfile:

gem 'jsonapi-resources'

Now, we have to update existing serializers. Rename PostSerializer to PostResource and put the following code:

class PostResource < JSONAPI::Resource
 has_many :comments
 attributes :title, :content
end

Do the same with the CommentSerializer (it should be named CommentResource now):

class CommentResource < JSONAPI::Resource
 attributes :author, :body
end

and we can generate the data for our JSON response:

post = Post.joins(:comments).first
JSONAPI::ResourceSerializer.new(PostResource, include: ['comments']).serialize_to_hash(PostResource.new(post, nil)) # => {:data=>{"id"=>"1", "type"=>"posts", "links"=>{:self=>"/posts/1"}, "attributes"=>{"title"=>"Title 0", "content"=>"Content 0"}, "relationships"=>{"comments"=>{:links=>{:self=>"/posts/1/relationships/comments", :related=>"/posts/1/comments"}, :data=>[{:type=>"comments", :id=>"25"}]}}}, :included=>[{"id"=>"25", "type"=>"comments", "links"=>{:self=>"/comments/25"}, "attributes"=>{"author"=>"Author 24", "body"=>"Comment 24"}}]}

JSON API Serializers

JSONAPI: Serializers is a simple library for serializing Ruby objects and their relationships.

Add the following line to your Gemfile:

gem 'jsonapi-serializers'

Now, we have to update existing serializers that we created above:

# app/serializers/post_serializer.rb

class PostSerializer
  include JSONAPI::Serializer

  attribute :id
  attribute :title
  attribute :content

  has_many :comments
end

# app/serializers/comment_serializer.rb

class CommentSerializer
  include JSONAPI::Serializer

  attribute :id
  attribute :author
  attribute :body
end

and we can generate the data for our JSON response:

post = Post.joins(:comments).first
JSONAPI::Serializer.serialize(post, include: ['comments']) # =>
{"data"=>{"type"=>"posts", "id"=>"12", "attributes"=>{"id"=>12,
"title"=>"Post", "content"=>"content"}, "links"=>{"self"=>"/posts/12"}, 
"relationships"=>{"comments"=>{"links"=>{"self"=>"/posts/12/relationships/comments", "related"=>"/posts/12/comments"}, 
"data"=>[{"type"=>"comments", "id"=>"201"}]}}}, 
"included"=>[{"type"=>"comments", "id"=>"201", 
"attributes"=>{"id"=>201, 
"author"=>"Author", "body"=>"Comment"}, 
"links"=>{"self"=>"/comments/201"}}]}

JBuilder

JBuilder is a gem that provides a simple DSL for declaring JSON structures. This time we don’t have to update our Gemfile because this gem is added by default to Rails. Usually, structures are stored in files with json.jbuilder extension so we have to create one. Enter the following code in app/views/post2.json.jbuilder file:

json.id post.id
json.title post.title
json.content post.content

json.comments(post.comments) do |comment|
  json.id comment.id
  json.author comment.author
  json.body comment.body
end

You can now load the template and generate JSON as well as use it without the template:

post = Post.joins(:comments).first

# With template

renderer = ApplicationController.new
renderer.render_to_string('/post2', locals: {post: post}) # => "{\"id\":114,\"title\":\"Title 0\",\"content\":\"Content 0\",\"comments\":[{\"id\":2727,\"author\":\"Author 24\",\"body\":\"Comment 24\"}]}"

# Without template

def jbuild(*args, &block)
  Jbuilder.new(*args, &block).attributes!
end

result = jbuild do |json|
  json.id post.id
  json.title post.title
  json.content post.content

  json.comments(post.comments) do |comment|
    json.id comment.id
    json.author comment.author
    json.body comment.body
  end
end

result # => {"id"=>12, "title"=>"Post", "content"=>"content", "comments"=>[{"id"=>201, "author"=>"Author", "body"=>"Comment"}]}

Serialization

Now that we've done with testing different Ruby gems, we move on to the second stage of the JSON serialization process: the hash transformation to the JSON format. The most popular solutions are:

Oj

Oj is a fast JSON parser and Object marshaller as a Ruby gem. Let’s add the new line to our Gemfile in order to install it:

gem 'oj'

Now we can try to transform a hash to the JSON format:

hash = {name: "John Doe", email: "john@doe.com"}
Oj.dump(hash) # => "{\"name\":\"John Doe\",\"email\":\"john@doe.com\"}"

JSON

The JSON library is a standard serialization library for Ruby so we can try it directly in our console without adding any extra code:

hash = {name: "John Doe", email: "john@doe.com"}
JSON.generate(hash) # => "{\"name\":\"John Doe\",\"email\":\"john@doe.com\"}"

Yajl

Yajl is a streaming JSON parsing and encoding library for Ruby.

We have to bundle the gem before we can try it:

gem 'yajl-ruby'

Now we can serialize a sample hash:

require 'yajl'

hash = {name: "John Doe", email: "john@doe.com"}
Yajl::Encoder.encode(hash) # => "{\"name\":\"John Doe\",\"email\":\"john@doe.com\"}"

The best solutions for JSON serialization in Rails

At this point, we’ve gone through the most popular data preparation and JSON serialization tools. Now, it’s time to choose the most performant pair. I will compare the speed of data preparation and JSON serialization using the code presented in the above paragraphs.

To test, I will create 100 Post objects and each post will have 25 comments:

100.times do |i|
  post = Post.create!(title: "Title #{i}", content: "Content #{i}")
  25.times do |i2|
    Comment.create!(post: post, author: "Author #{i2}", body: "Comment #{i2}")
  end
end

I will use Ruby Benchmark module to compare the data preparation time for each tool.

Data preparation tools

Below are the solutions ranked fastest to slowest:

 

Solution name

Time

FAST JSON API

0.121921s

Active Model Serializers

0.154672s

JSONAPI-RB

0.246346s

JSON API Serializers

0.262120s

JSON API Resources

0.290525

RABL

0.853417s

JBuilder

2.559193s

The fastest solution is the Fast JSON API and the slowest solutions are JBuilder with RABL.

Fortunately, FAST JSON API is also very intuitive and does not require any extra configuration. All we need to do to start using it is define attributes and associations for given model.

The code used for tests is available here.

JSON serialization solutions

Solution name Time (seconds)
Oj 0.007225s
Yajl 0.014289s
JSON 0.036572s

The fastest solution is the Oj library. It’s several times faster than its opponents, making it the clear winner.

Implementing fast JSON serialization in a Ruby on Rails application

After our testing, we have our winning couple: Fast JSON API and Oj. It’s time to use both solutions in a Ruby on Rails application to demonstrate how to create performant JSON serialization.

Let’s start by adding required gems to our Gemfile:

gem 'oj'
gem 'fast_jsonapi'

and running bundle install

Controller creation

Previously, we created a bunch of posts and related comments, so now it’s time to create an endpoint where we provide data in the JSON format. In order to do this, let’s add a new controller class app/controllers/posts_controller.rb with the following contents:

class PostsController < ApplicationController
  def index
    posts = Post.joins(:comments)
  end
end

Serializers creation

In order to serialize our posts and comments data, we have to define serializers with the attributes we want to serialize. The following code should look familiar, since we used it before when demonstrating the usage of the gem:

# app/serializers/post_serializer.rb

class PostSerializer
  include FastJsonapi::ObjectSerializer
  attributes :title, :content
  has_many :comments
end

# app/serializers/comment_serializer.rb

class CommentSerializer
  include JSONAPI::Serializer

  attribute :id
  attribute :author
  attribute :body
end

Oj gem usage

Since the fast_jsonapi gem includes Oj automatically, we don't have to explicitly tell our Rails app to include Oj  - https://github.com/Netflix/fast_jsonapi/blob/master/lib/fast_jsonapi/multi_to_json.rb#L57.

Preparing response in the JSON format

We have our serializers prepared so the last thing to do is to render the posts in the JSON format. In order to do this we have to edit our previously created controller:

class PostsController < ApplicationControlle
  def index
    posts = Post.joins(:comments)
    render json: PostSerializer.new(posts).serialized_json
  end
end

and let Rails know that we want to access it by editing the config/routes.rb file:

resources :posts, only: [:index]

Now you can run rails s command and access generated data in the JSON format by using http://localhost:3000/posts.json URL.

Key takeaways

In this article:

  • We compared six of the most popular data preparation tools for JSON serialization and chose one.
  • We compared three of the most popular JSON serialization tools and we chose the one.
  • We implemented fast JSON serialization mechanism in a Ruby on Rails application using the best available solutions

Our solution is extendable and very fast. This approach places serialization logic on a model layer and requires us to directly specify which serializer we want to use. Importantly, you don’t need extra configuration to get started. Using the base code allows you to provide a complete and rich JSON response in your controllers.

Receive tutorials, informative articles, and ButterCMS updates to keep your work running smoothly.
    

Related Articles