Rails to_json or as_json?
A really great modification was introduced in Rails 2.3.3 - and while everyone clamored about JSON encoding speeds and C vs Ruby implementations, the blogosphere overlooked the clean separation of responsibility that was introduced.
In the “old days”, you’d override to_json
in your model class to provide a JSON implementation of your model. Then in your controller, render :json => @model
would work perfectly. And some folks would even redundantly code render :json => @model.to_json
, and that would work too.
to_json
even had some great options for ActiveRecord objects! You could tell the method to only render certain attributes, or to include associations or method calls!
render :json =>
@user.to_json(:only => [:email], :include => [:addresses])
Life was good. But things start to fall apart when you want to do something a little out of the ordinary. Like return JSON with the model as part of a bigger structure.
render :json => { :success => true,
:user => @user.to_json(:only => [:email]) }
Oops. {\"user\":{\"email\":\"me@example.com\","success":true}
has the JSON characters escaped, which is not what we want. So what do we do? We hack around it:
render :json => { :success => true,
:user => { :email => @user.email } }
But this doesn’t scale - we have to explicitly create the JSON by hand in the controller. What if we need 5 or more attributes? Yuck!
Enter ActiveSupport 2.3.3. Now the creation of the json is separate from the rendering of the json. as_json
is used to create the structure of the JSON as a Hash, and the rendering of that hash into a JSON string is left up to ActiveSupport::json.encode
. You should never use to_json
to create a representation, only to consume the representation.
def as_json(options={})
{ :email => self.email }
end
Anytime to_json
is called on an object, as_json
is invoked to create the data structure, and then that hash is encoded as a JSON string using ActiveSupport::json.encode
. This happens for all types: Object, Numeric, Date, String, etc (see active_support/json).
ActiveRecord objects behave the same way. There is a default as_json
implementation that creates a Hash that includes all the model’s attributes. You should override as_json
in your Model to create the JSON structure you want. as_json
, just like the old to_json
, takes an option hash where you can specify attributes and methods to include declaratively.
def as_json(options={})
super(:only => [:email, :avatar], :include =>[:addresses])
end
Your controller code to display one model should always look like this:
render :json => @user
And if you have to do anything out of the ordinary, call as_json
passing your options.
render :json => { :success => true,
:user => @user.as_json(:only => [:email]) }
The moral of the story is: In controllers, do not call to_json
directly, allow render
to do that for you. If you need to tweak the JSON output, override as_json
in your model, or call as_json
directly.
Fix your code now to use as_json
- it will be one less thing to worry about when you migrate to Rails 3.
This post was inspired by the investigation I went into while exploring the answer to this question on Stack Overflow.
Hey Brian Morearty - Rails was never Javaficated to begin with. So the answer is yes.