Back to Tutorials
gems|Jutsu>JSON6/8/2025

Clean JSON generation with JBuilder (Rails 4)

After talking about Active Model Serializers, let's see an alternative.

The goals are the same as Active Model Serializers :

  • Deliver JSON through your API
  • Separate the presentation concern from the model
  • Control what goes in your JSON

Jbuilder represents a totally different approach. Where Active Model Serializers use simple Ruby objects, Jbuilder offers a simple DSL that let you write JSON 'views'.

Let's see how it works by building a Rails API !

Source Code

Available here.

Setup the application

Since I am building an API here, I will use the rails-api gem.

rails-api new jbuilder_tuto

If you don't have rails-api, you can install it with gem install rails-api. You can also use a standard Rails app if you prefer.

Create the models

Let's get started. We are going to create our models :

 rails g model Artist name:string label:string

 rails g model Album name:string release_date:date artist_id:integer

 rails g model Song name:string release_date:date lyrics:text album_id:integer

Migrate all that :

rake db:migrate

We have to add relations between our models :

# app/models/artist.rb
class Artist < ActiveRecord::Base
  has_many :albums
end

# app/models/album.rb
class Album < ActiveRecord::Base
  belongs_to :artist
  has_many :songs
end

# app/models/song.rb
class Song < ActiveRecord::Base
  belongs_to :album
end

JBuilder

Now, we're going to add JBuilder and start making some cool JSON ! Note that if you are using rails instead of rails-api, jbuilder will already be in your Gemfile !

# Gemfile.rb
# ...
gem 'jbuilder'
# ...

'bundle install' and restart your application.

The Data

Here is a set of data you can easily put in seed.rb and import with 'rake db:seed'(It's the same than for Active Model Serializers) :

muse = Artist.create( name: 'Muse', label: 'Warner Bros.')
black = muse.albums.create( name: 'Black Holes and Revelations', release_date: '03/07/2006' )

resistance = muse.albums.create( name: 'The Resistance', release_date: '11/09/2009' )
["Take a Bow", "Starlight", "Supermassive Black Hole", "Map of the Problematique", "Soldier's Poem", "Invincible", "Assassin", "Exo-Politics", "City of Delusion", "Hoodoo", "Knights of Cydonia"].each do |song|
  resistance.songs.create( name: song, release_date: resistance.release_date, lyrics: '...' )
end

red = Artist.create( name: 'Red Hot Chili Peppers', label: 'EMI')
californication = red.albums.create( name: 'Californication', release_date: '08/06/1999' )

["Around the World" , "Parallel Universe", "Scar Tissue", "Otherside", "Get on Top" , "Californication", "Easily" , "Porcelain", "Emit Remmus", "I Like Dirt", "This Velvet Glove", "Savior" , "Purple Stain" , "Right on Time", "Road Trippin"].each do |song|
  californication.songs.create( name: song, release_date: californication.release_date, lyrics: '...' )
end

Now, we need to create a controller that will be responsible for rendering the views.

The Artist Controller

If you are using rails-api, you will want to add one module to your ApplicationController :

# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  include ActionController::ImplicitRender
end

You don't need that if you are using a normal Rails app.

Now, let's add the controller :

# app/controllers/artists_controller.rb
class ArtistsController < ApplicationController

  def index
    @artists = Artist.all
  end

end

Update the routes :

Rails.application.routes.draw do
  resources :artists
end

The last missing part is the actual view. We're going to create it in'app/views/artists/' and it will contain the rendering logic of our JSON.

Here's the content :

# app/views/artists/index.json.jbuilder
json.artists @artists

Now go to http://localhost:3000/artists.json and you should see the following :

JSON representing artists information.

Great ! But you probably don't want to show the timestamps like this. What if I want a description containing the name of the band and the label producing them ? Can we do that ? Short answer, yes !

First, if we want just the id, name and label we can do this :

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.id artist.id
  json.name artist.name
  json.label artist.label
end

or shorter :

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.(artist, :id, :name, :label)
end

or if you don't like the previous syntax :

# app/views/artists/index.json.jbuilder
json.array! @artists do |artist|
  json.extract! artist, :id, :name, :label
end

or if you like to type a lot :

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.set! :id, artist.id
  json.set! :name, artist.name
  json.set! :label, artist.label
end

You can also create methods in your models and use them in the jbuilder template :

# app/models/artist.rb
class Artist < ActiveRecord::Base
  has_many :albums

  def name_with_label
    "#{name} produced by #{label}"
  end

end

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.(artist, :name_with_label)
end

Result :

jbuilder6

We can also put it in a helper. Jbuilder templates behave like regular html templates so you can use any helper method.


If you use rails-api and you want to use helpers, you need to add this to your ApplicationController and create the app/helpers folder:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  include ActionController::ImplicitRender
  include ActionController::Helpers
end

Now create 'app/helpers/artists_helper.rb' if you don't have it :

# app/helpers/artists_helper.rb
module ArtistsHelper

  def name_with_label(artist)
    "#{artist.name} produced by #{artist.label}"
  end

end

With this, you can just do :

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.(artist, :id, :name, :label)
  json.name_with_label name_with_label(artist)
end

Another cool thing is that Jbuilder handles conditions.

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.(artist, :id, :name, :label)

  if artist.id == 1
    json.name_with_label name_with_label(artist)
  end
end

jbuilder7

And loops ! (even useless ones...)

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.(artist, :id, :name, :label)

  5.times do |i|
    json.set! "count_#{i}", i
  end
end

Ok, enough fun. You can do pretty much everything you would do in a regular html views inside Jbuilder templates. Let's see how to handle embedded models.

Well, it's actually super easy :

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.(artist, :id, :name, :label)

  json.albums artist.albums do |album|
    json.(album, :id, :name)
  end
end

Will give you :

jbuilder8

The problem is that it's not reusable... Good thing, Jbuilder can handle partials !

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.(artist, :id, :name, :label)
  json.albums artist.albums, partial: 'albums/album', as: :album
end

# app/views/albums/_album.json.jbuilder
json.(album, :id, :name)

Et voila :

jbuilder9

Let's add the songs inside the albums :

# app/views/albums/_album.json.jbuilder
json.(album, :id, :name)
json.songs album.songs, partial: 'songs/song', as: :song

# app/views/songs/_song.json.jbuilder
json.(song, :id, :name, :lyrics)

jbuilder10

As you can see Jbuilder is really easy to use and let you build completly customizable JSONs !

Here are a few more tips.

array!

You can use 'array!' to create, well, an array :

# app/views/artists/index.json.jbuilder
json.array! @artists do |artist|
  json.(artist, :id, :name, :label)
end

This will give you an array of artists, without 'artist: { ... }'.

null!

Want a null value ? No problem !

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  if artist.id == 0
    json.(artist, :id, :name, :label)
    json.albums artist.albums, partial: 'albums/album', as: :album
  else
    json.null!
  end
end

Will give a beautiful '[null, null]' array. Please, please, write smarter conditions than me...

cache!


Required for rails-api :

# app/controllers/application_controller.rb

class ApplicationController < ActionController::API
    include ActionController::ImplicitRender
    include ActionController::Helpers
    include ActionController::Caching
end

Fragment caching works just like in HTML Templates :

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.cache! ['v1', artist], expires_in: 1.minutes do
    json.(artist, :id, :name, :label)
    json.albums artist.albums, partial: 'albums/album', as: :album
  end
end

What's that ? Why use caching ? Well, to give you an example, on the very simple/small json of this tutorial, it divided the request duration by 4 (12ms to 4ms). Now, imagine with a huge JSON !

cache_if!

With 'cache_if!', you can cache only if a specific condition is met :

# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
  json.cache_if! artist.id == 1, ['v1', artist], expires_in: 1.minutes do
    json.(artist, :id, :name, :label)
    json.albums artist.albums, partial: 'albums/album', as: :album
  end
end

key_format!

Don't like snake_case ? You can camelize the keys if you prefer.

# app/views/artists/index.json.jbuilder
json.key_format! camelize: :lower
json.this_is_madness 'This. is. Sparta!'
json.artists @artists do |artist|
  json.(artist, :id, :name, :label)
  json.albums artist.albums, partial: 'albums/album', as: :album
end

The End

Well, I think that's about it. Lots of good things here. Now, you know how to use yet another JSON builder ! :D

Want faster JSON ?

Checkout Oj (Optimized JSON) by Peter Ohler.

Source Code #2

Available here.

Comments

Loading comments...

Level Up Your Dev Skills & Income 💰💻

Learn how to sharpen your programming skills, monetize your expertise, and build a future-proof career — through freelancing, SaaS, digital products, or high-paying jobs.

Join 3,000+ developers learning how to earn more, improve their skills, and future-proof their careers.

Clean JSON generation with JBuilder (Rails 4) | Devmystify