Gowalla.com

Gowalla Engineering

Boxer: Generating Sane JSON from Complex Objects for Our API

Written by Brad Fults

The Problem

Here at Gowalla we have a large API with many different types of objects distributed across many different resources. So composing and showing views of those objects for API responses has been a challenge.

We started out by just rendering ActiveRecord models directly to JSON:

class Api::UsersController < ApiController
  def show
    @user = User.find(params[:id])
    render :json => @user
  end
end

But that quickly became unwieldy as we wanted full and simple control over which attributes are returned, and to start returning method values alongside model attributes. So we moved on to writing .json.erb files, which allowed us to specify exactly what appears in a response:

# app/views/users/show.json.erb
<%= raw({
  :name => @user.name,
  :recent_spots => @user.recent_spots(30),
  :is_private => @user.private?
}.to_json) %>

Though after a long while, these views ended up growing out of control, with conditionals based on the authenticated user requesting the information, whether they're friends, whether the user resource is a business, whether a promotion is running, etc.

# app/views/users/show.json.erb
<%
  hash = {
    :name => @user.name,
    :is_private => @user.private?
  }
  if user.public?
    hash.merge!({
      :recent_spots => @user.recent_spots(30),
      :recent_highlights => @user.recent_highlights(10),
    })
  end
  if user.friends?(current_user)
    hash.merge!({
      :is_friend => true,
      :mutual_friends => user.mutual_friends(current_user).map(&:to_hash),
    })
  end
%>
<%= raw(hash.to_json) %>

The Solution

So during our developing of Gowalla 4, we came across Nathan Esquenazi's RABL library, which is a great attempt at attacking this problem. For our purposes, though, we went with a simpler and less robust framework that would do exactly what we wanted.

Our simple framework, Boxer, knows about different object representations, the various views each of our objects can have and will let us compose objects together in responses that satisfy the complexity of our large API.

Instead of defining messy conditional views full of merged hashes, Boxer provides a clean framework for describing the representation and views of an object as hashes:

Boxer.box(:user) do |box, user|
  box.view(:base) do
    {
      :name => user.name,
      :age  => user.age,
    }
  end

  box.view(:full, :extends => :base) do
    {
      :email      => user.email,
      :is_private => user.private?,
    }
  end
end

Then you can "ship" the contents of a box with a model object (or any other arguments you want to pass in) and get a JSON-ready hash:

>> Boxer.ship(:user, User.first, :view => :full).to_json
=> "{"name": "Bo Jim", "age": 17, "email": "b@a.com", "is_private": false}"

In our case, our controllers do more or less a straight render of a shipped box:

def show
  @object = Object.find(params[:id])
  render :json => Boxer.ship(:object, @object, :view => :full)
end

For more examples and a more in-depth explanation, see the Boxer README and the project wiki.

Boxer also goes through the trouble of solving small and common problems that creep up when using this style of framework. Things like preconditions, helper methods, including methods for use in boxes and multiple box inheritance.

We have released Boxer as a gem and, as noted, an open-source project on GitHub. Let us know what interesting uses you find for Boxer, in addition to any suggested improvements.

About Brad Fults

Brad Fults

Platform Developer. Background in JavaScript and front-end web development. Prefers simplicity, directness, red wine, fast cars and is healthily obsessed with quality.

Gowalla
Gowalla Passport
GitHub
Github
Twitter
Twitter

Other Articles