GSD
JSON Serialization in Rails: A Complete Guide
Posted by Paweł Dąbrowski on December 12, 2024
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.
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 JSON serialization in a Rails application
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 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 easyway to slow down your data preparation process.
The most popular data preparation tools
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.
These tools range from basic to advanced, offering different levels of functionality. For example, Active Model Serialization is the native approach, and it requires you to set up just one gem and is well-documented on the official Rails site.
On the other hand, jsonapi-rb relies on four separate micro-libraries to deliver a wide range of features. Fast JSON API is known for its speed and ease of use, making it a great choice for beginners. If you are looking for the most advanced capabilities, JSONAPI::Serializers is worth exploring. Lastly, if your goal is to serialize attributes directly to a database, Active Record Serialization is the obvious choice.
The following sections will highlight these differences and characteristics in detail, helping you make an informed decision.
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:
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"}}]}
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"}]}
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 (aka Serialization)
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"=>...}
The most popular serialization tools
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.
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) for 500,000 iterations |
Oj | 0.31s |
Yajl | 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
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: 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
Step 3: Serializers creation
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: Using the Oj gem
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: 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 < 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 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.
Learn how you can use a headless cms with your next Rails app here.
ButterCMS is the #1 rated Headless CMS
Related articles
Don’t miss a single post
Get our latest articles, stay updated!
Paweł is a self-made writer of the digital era, IT samurai and problem solver who loves Ruby. He writes for human beings and computers at pdabrowski.com and focuses on soft skills in the software creation process.