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

In: JavaScript|Ruby on Rails

10 Oct 2007

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 :).

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

Avatar

Better JSON output from Rails with the Jsonifier plugin - redemption in a blog

October 15th, 2007 at 10pm

[…] 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 […]

Avatar

Riccardo Govoni

October 16th, 2007 at 7am

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 !

Avatar

Chu Yeow

October 16th, 2007 at 10am

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?

Avatar

Riccardo Govoni

October 16th, 2007 at 1pm

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.

Avatar

hubert

November 21st, 2007 at 1pm

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

Avatar

Sebastian Winkler

December 5th, 2007 at 12am

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?

Avatar

Alex Egg

December 12th, 2007 at 1pm

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?

Avatar

Chu Yeow

December 12th, 2007 at 1pm

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

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

Avatar

Chu Yeow

December 12th, 2007 at 1pm

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.

Avatar

Gregory

January 5th, 2008 at 6am

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

Avatar

Alex Egg

January 19th, 2008 at 4pm

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

Avatar

Chu Yeow

January 22nd, 2008 at 11pm

Avatar

Brian

March 18th, 2008 at 8pm

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

Avatar

The Irish Penguin » Blog Archive » Quick Example of Serialisation via to_json in Ruby On Rails

June 21st, 2008 at 9pm

[…] Note: There’s  some great JSON support since Rails 2 has come around. For more info on this see here. […]