New on edge Rails: JSON serialization of ActiveRecord objects reaches maturity

The last time I wrote about ActiveRecord#to_json on edge Rails, it was missing some key functionality. For one, you couldn’t include any associations. Another thing was you couldn’t do something like this in your controller:

@authors = Author.find(:all)

render :json => @authors.to_json(:only => :name)

Oh and did I mention it wasn’t emitting valid JSON by default?

In the last few weeks, several patches to the Rails trunk have fixed all these issues (most of these patches are already on the Rails 2.0 Preview Release). JSON serialization of ActiveRecord objects is finally on par with XML serialization, and that old Jsonifier plugin I wrote can finally be sent off to the plugin retirement home (though, I am thinking of porting all the JSON patches into Jsonifier to add Rails 2.0-like JSON serialization to Rails 1.2.4, but who knows, I am lazy).

Anyway, back to the new JSON stuff on edge Rails.

No more invalid JSON from Rails

Yes it’s true. No more changing of module attribute to get Rails to produce valid JSON. Thanks to Choon Keat with his detailed SVN history snooping, we finally managed to get a patch to fix JSON encoding to quote all hash keys into Rails.

:include option can be used to include associations with ActiveRecord::Base#to_json

At DHH’s urging, I submitted a patch that added the :include option to ActiveRecord#to_json. Of course, it was a sloppy first submission and DHH pointed that out - a little bit of DRY love and it was really great to see it get committed (I have a soft spot for JSON serialization from Rails, since I’d been trying to achieve the same thing with Jsonifier).

So yes, now you can do:

json = @david.to_json(:include => :posts)
# or even...
json = @david.to_json(
  :include => {
    :posts => {
      :include => {
        :taggings => {
          :include => {
            :tag => { :only => :name }
          }
        }
      }
    }
})

Enumerable#to_json and Hash#to_json now accept options

One problem with Jsonifier that I often get emails about is how the to_json options don’t work for lists (Enumerables). And why is this important? Well, we often retrieve collections of ActiveRecord objects at a time with AR::B#find so it’s not too much to ask to be able to do this in your controller actions:

@authors = Author.find(:all)

render :json => @authors.to_json(:only => :name)

But of course you couldn’t, so I patched away by changing the to_json methods of Enumerable and Hash (and other types as well) to accept an optional options argument. Enumerables will pass on any options it receives to its elements. Hashes will respect :only and :except (as well as passing on the options to its elements).

Unambiguous (non-US-centric) dates and times

While Rails 1.2.3 (and 1.2.4) never supported encoding of Dates, Times and DateTimes, edge Rails has been happily supporting Date, Time and DateTime conversions to JSON since revision 6673. Unfortunately, the date string format is in MM/DD/YYYY format:

Time.now.to_json
=> "\"09/21/2007 12:15:02 UTC\""

Geoff Buesing suggested that Rails should change this to the more unambiguous YYYY/MM/DD format. Finding the right date format is important not only for readability reasons, it’s also important that the date string is directly parsable by the many browser JavaScript implementations using the JavaScript new Date(dateString) function. There is no formal spec of how dates should be represented in JSON, but it’s most convenient that strings representing dates can be directly converted into JavaScript Date objects.

Testing in several browsers and on different platforms, albeit not extensively, we settled on the %Y/%m/%d %H:%M:%S %z (strftime) format (more details in the comments of the Trac ticket).

Now it is:

Time.now.to_json
=> "\"2007/09/21 12:15:02 +0800\""

Much better.

What’s left?

Documentation, for one thing. ActiveRecord::Base#to_json is still undocumented (docfix patch). The to_json methods for Enumerable and Hash need documentation as well (will patch that soon).

ActiveRecord::Base#to_json also doesn’t deal with binary attributes, which can be easily resolved by Base64-encoding them (patch coming soon, it gets slightly complicated when trying to do a from_json). And of course, optimization improvements are always nice (decoding from JSON especially).

So, if you use JSON at all, please come in and help patch the remaining bits before Rails 2.0 rolls out. If you can’t (or more realistically, don’t have the time to) patch Rails yourself, do help out by verifying patches :).

13 Comments & TrackBacks (Add yours)

The paper doll icon that precedes each comment is an idea conceived by Vanessa Tan.

Paper doll icon
Better JSON output from Rails with the Jsonifier plugin - redemption in a blog's Gravatar

[…] Edge Rails now does whatever Jsonifier does (and more). Check out my blog post on JSON serialization maturity in edge Rails. This means that this plugin is almost obsolete if you are using edge Rails and don’t require […]

Posted by: Better JSON output from Rails with the Jsonifier plugin - redemption in a blog on October 15, 2007 10pm

Paper doll icon
Riccardo Govoni's Gravatar

Hi, I have recently worked with some code which needed ActiveRecord to JSON conversion and viceversa. At the end, I produced my very rough solution, which you can find at this page .

The nice thing is that I am able to convert to_json but also from_json, recovering or creating the activerecord counterpart as needed.

I am too newbie on rails to understand if it can be of any use for anyone, but I accept every kind of comment !

Posted by: Riccardo Govoni on October 16, 2007 7am

Paper doll icon
Chu Yeow's Gravatar

Edge Rails allows you to do ActiveRecord::Base#from_json as well :).

But, very nice stuff you’ve got there, ever thought about rolling it into a plugin for Rails 1.x?

Posted by: Chu Yeow on October 16, 2007 10am

Paper doll icon
Riccardo Govoni's Gravatar

Ok, thanks for the heads up about Edge Rails. About the plugin, I never created one, but I will take a look at how to do it and eventually ship it as “extra stuff” within my project.

Posted by: Riccardo Govoni on October 16, 2007 1pm

Paper doll icon
hubert's Gravatar

i recently updated the restaurant model in my rails app so that it now has two associations, :has_many => :hours and :has_and_belongs_to_many => :cuisines. is there a way to automate json serialization of the restaurant object using the new to_json method?

from the example given, it’s clear you can do single associations and nested single associations, but can you do multiple associations as well?

thanks for any suggestions…

-hubert

Posted by: hubert on November 21, 2007 1pm

Paper doll icon
Sebastian Winkler's Gravatar

Are you saying that instead of ISO 8601 Rails’ JSON will be using some custom home-brew date format, because it seems “more unambiguous” than the crazy American format?!
I am stunned!
Whose idea was that?

Posted by: Sebastian Winkler on December 5, 2007 12am

Paper doll icon
Alex Egg's Gravatar

Hey,

I tried to find you on #rubyonrails to ask you this.

Shouldn’t this work:

@sub_category.to_json(:include => [:category, {:subscriptions => { :include => :subscriber }}])

This simply including 2 attributes of sub_category: category and subscriptions. And then includes the subscriber for all subscriptions.

This fails: NoMethodError - The error occurred while evaluating nil.macro):

If I remove the category so it’s like this:

@sub_category.to_json(:include => [{:subscriptions => { :include => :subscriber }}])

The serialization works like a champ. If I add category back and remove the subscribers, it works also:

@sub_category.to_json(:include => [:category, :subscriptions ])

There seems to be something wrong with the combination of an include with an an array item including a higher order association.

What do you say? Is my syntax off?

Posted by: Alex Egg on December 12, 2007 1pm

Paper doll icon
Chu Yeow's Gravatar

Ah, I believe the syntax you are looking for is this:

@sub_category.to_json(:include => [
  :category,
  :subscriptions => {
    :include => :subscriber
  }
])

Posted by: Chu Yeow on December 12, 2007 1pm

Paper doll icon
Chu Yeow's Gravatar

My bad it should be:

@sub_category.to_json(
  :include => [
    :category,
    :subscriptions => {
      :include => :subscriber
    }
  ]
)

More examples at http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html#M001111

Good point though, I didn’t write a test for this and it is rather tricky syntax.

Posted by: Chu Yeow on December 12, 2007 1pm

Paper doll icon
Gregory's Gravatar

Hmmm… I am working with Rails 2.0.2, and the JSON support still seems to be a little rough around the edges. Check this out:

>> ActiveSupport::JSON.decode(ActiveSupport::JSON.encode(”"))
=> “\\u003C\\u003E”

Not idempotent. Oh my!

// Gregory

Posted by: Gregory on January 5, 2008 6am

Paper doll icon
Alex Egg's Gravatar

Did you get a chance to update the tests for the issue I was describing?

http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/37c42ddec5d45b2

Posted by: Alex Egg on January 19, 2008 4pm

Paper doll icon
Chu Yeow's Gravatar

Yup finally did: http://dev.rubyonrails.org/ticket/10895

Posted by: Chu Yeow on January 22, 2008 11pm

Paper doll icon
Brian's Gravatar

Is is possible to cross-domain post JSON? Do you have an examples?

Posted by: Brian on March 18, 2008 8pm

You can subscribe to the RSS feed for comments on this post.

Post a comment

(required)

(required, but never displayed)


You can format your comments using XHTML. Your email address will not be displayed or used for nefarious purposes.

Only following tags are allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>