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")

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"}]}

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"}}]}

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 (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"}]}

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"}}]}

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 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 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\"}"

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 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.