ButterCMS Logo

JSON serialization in Rails: A complete guide

Published on
Updated on
14 min read
Featured Image: JSON Serialization in Rails: A Complete Guide
CMS Developer Tutorials

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

Table of contents

What is JSON serialization?

JavaScript Object Notation (JSON) is a data format that encodes objects in a string. Such data representation can easily be translated between servers and browsers. Serialization in Rails, and other languages, is a process that transforms an object into that string. 

If you’re looking to streamline the initial setup of your Rails projects, consider checking out the ButterCMS Rails starter project. While it’s not directly related to serialization, it provides a solid foundation to get your application up and running faster, allowing you to focus on building out your features with ease.

Why you need a Rails JSON serializer

In Rails, you can use a serializer to define how data from your models should be formatted when sent as a response in JSON. It acts as a blueprint to control what attributes or relationships of a model are included in the output. 

This is particularly handy for APIs, where you need to send structured data to clients. For example, instead of exposing the entire model, you can choose specific fields or even modify how certain attributes appear in the response.

To make matters easier for developers, Rails ships with a native serializer known as the Active Model Serializer (gem ‘active_model_serializers’). It’s open-source, well-documented, extensible, and easy to use.

The two stages of Rails JSON serialization

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

 In the first stage (data preparation), we define how we want our data to be serialized. In the second step (transformation), the actual serialization takes place. 

Stage 1: Data preparation

In this stage, we define an attributes hash that lists the fields to include in the serialized output of the object. The keys of this hash must be strings (not symbols), and their corresponding values can be nil initially.

When serialization is triggered, the attributes hash is checked to determine what data to include. The serializer uses instance methods with names matching the keys in the attributes hash to fetch the data. Let’s consider a simple example:

class Person
  include ActiveModel::Serialization

  attr_accessor :name
  attr_accessor :age

  def attributes
	{ 'name' => nil }
  end
end

person = Person.new
person.serializable_hash   # => {"name"=>nil}
person.name = "Bob"
person.serializable_hash   # => {"name"=>"Bob"}

Here, the attributes hash acts as a blueprint for the data preparation stage. 

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 easy way to slow down your data preparation process.

Popular Rails JSON serializer gems (comparison)

Ruby on Rails offers several gems to simplify and customize data preparation for your application. While their APIs share some similarities, each gem has distinct features and capabilities. In this guide, I’ll cover some of the most popular data preparation tools and compare them to help you decide on the best solution for your needs.

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:

gem install rails
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

ActiveModel::Serialization implementation is the native serialization method in Rails. It’s also the most popular. Let’s start with adding the gem into our Gemfile:

gem 'active_model_serializers'

Now, here’s how we can update our Post and Comment models to make them serializable: 

# app/serializers/post_serializer.rb

class PostSerializer
  include ActiveModel::Serializers::JSON

  attr_accessor :id, :title, :content, :comments

  def attributes
	{ 'id' => nil, 'title' => nil, 'content' => nil, 'comments' => nil }
  end

  def comments
	@comments.map { |comment| comment.serializable_hash }
  end
end

# app/serializers/comment_serializer.rb
class CommentSerializer
  include ActiveModel::Serializers::JSON

  attr_accessor :id, :body, :author

  def attributes
	{ 'id' => nil, 'body' => nil, 'author' => nil }
  end
end

With the above changes made, we can test our serializers this way:

# Testing the serializers
comment1 = CommentSerializer.new
comment1.id = 201
comment1.body = "Comment"
comment1.author = "Author"

post = PostSerializer.new
post.id = 12
post.title = "Post"
post.content = "content"
post.comments = [comment1]

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

post.to_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"}}]}

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

Active Record Serialization

The ActiveRecord component provides built-in support for serializing attributes directly in the database. This is done using the serialize method, which can handle formats like JSON, YAML, or even custom serializers. If you want to serialize a class to your database, this is the functionality you should use. 

For example, here’s how you could implement custom JSON serialization for a sample class:

class Profile
  attr_reader :bio, :website, :social_links

  # Deserialize JSON data into a Profile instance
  def self.load(payload)
	data = payload.present? ? JSON.parse(payload) : {}
	new(data["bio"], data["website"], data["social_links"])
  end

  # Serialize a Profile instance into JSON
  def self.dump(profile)
	return if profile.nil?

	JSON.dump(
  	{
    	"bio" => profile.bio,
    	"website" => profile.website,
    	"social_links" => profile.social_links
  	}
	)
  end

  def initialize(bio, website, social_links = {})
	@bio = bio
	@website = website
	@social_links = social_links
  end
end

class User < ActiveRecord::Base
  # Use the Profile class for custom serialization with JSON
  serialize :profile, coder: Profile
end

After saving the user, the profile column in the database will contain JSON data like this:

{
  "bio": "Ruby developer and tech enthusiast.",
  "website": "https://janedoe.dev",
  "social_links": {
	"twitter": "jane_doe",
	"github": "janedoe"
  }
}

Stage 2: Transformation to JSON

After data preparation, the object is serialized into JSON or other formats. This process converts the hash generated in the preparation stage into a final JSON string. Rails provides methods like to_json and as_json for this purpose. Here’s an example of how a typical JSON serialization looks:

class Person
  include ActiveModel::Serializers::JSON

  attr_accessor :name
  attr_accessor :age

  def attributes
	{ 'name' => nil }
  end
end

person = Person.new
person.to_json         	# => "{\"name\":null}"
person.name = "Bob"
person.to_json         	# => "{\"name\":\"Bob\"}"

Rails also supports options like :only, :except, :methods, and :include to customize the serialized output. For example:

person.serializable_hash(only: 'name')            	# => {"name"=>"Bob"}
person.serializable_hash(include: :address)      	# => {"name"=>"Bob", "address"=>...}

Ruby gems for JSON transformation

Next, we will explore the different Ruby gems that can be used to implement serialization, i.e., the transformation of the generated hash to the JSON format. 

Read also: How to build landing pages with Ruby and ButterCMS

Oj (fastest Rails JSON serializer)

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) # =&gt; "{\"name\":\"John Doe\",\"email\":\"john@doe.com\"}"

JSON (Standard)

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) # =&gt; "{\"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) # =&gt; "{\"name\":\"John Doe\",\"email\":\"john@doe.com\"}"

Benchmark results: Best Rails JSON serializer performance

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 ranking

Below are the solutions ranked fastest to slowest:

Solution name Time
Fast JSON API .121921s
JSON API Serializers .262120s
Active Model Serializers .154672s
JSONAPI-RB .246346s

The fastest solution is the Fast JSON API. 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 libraries ranking

Solution name Time (seconds) for 500,000 iterations
Oj 0.31s
Yaji 0.69s
JSON 1.73s

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.

Step 1: Adding the gems (fast_jsonapi and oj)

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

gem 'oj'
gem 'fast_jsonapi'

Once you have added the gems, install them by issuing the `bundle install` command.

Step 2: Creating a Rails controller with JSON responses

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 &lt; ApplicationController
  def index
    posts = Post.joins(:comments)
  end
end

Step 3: Building a Ruby on Rails serializer

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

Step 4: Optimizing Rails JSON with Oj

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.

Step 5: Returning JSON in Rails API Endpoints

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 &lt; ApplicationController
  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 the most popular data preparation and serialization tools to find the two best options.

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

Add a Ruby CMS to your project using a simple starter kit. Or learn how to build a Rails knowledge base in this tutorial. 

Author

Maab is an experienced software engineer who specializes in explaining technical topics to a wider audience.