Understanding Ruby and Rails: Serializing Ruby objects with JSON

April 29th, 2010 at 11:29 am • permalink17 comments

This article targets Rails 2.3 and Rails 3. The information contained in this page might not apply to different versions.

This is article is part of my series Understanding Ruby and Rails. Please see the table of contents for the series to view the list of all posts.

A few weeks ago, Alan Skorkin posted a nice article about serializing objects with Ruby introducing different serialization options, including YAML, Marshaling and JSON. This reminded me of an article I always wanted to write about ActiveSupport::JSON module, as part of the inside Ruby on Rails series.

What is JSON?

JSON (JavaScript Object Notation) is a lightweight data-interchange format. More important, JSON is a human readable serialization format, like the popular YAML format all Rubyist are probably familiar with.

Compared to other serialization alternatives such as XML, YAML or Binary-serialization, JSON offers the following advantages:

  • it’s a human readable format
  • it’s largely adopted and supported by the most part of programming languages
  • it’s a language-independent format
  • can be compressed in one line to reduce stream size
  • can represent the most part of standard objects
  • seamlessly integrates with JavaScript which makes JSON the standard for streaming data over AJAX calls

All these features make JSON an excellent serialization format. Of course, there are also some drawbacks, but this is material for an other article.

JSON and Ruby on Rails

Ruby on Rails is a web application framework and JSON is strictly related to the web ecosystem as a subset of the JavaScript programming language. There are many different parts of a Ruby on Rails application where you might need to manipulate, encode and decode a JSON string into a Ruby object and vice-versa.

JSON support in Ruby on Rails is provided by the ActiveSupport::JSON module. Behind the scenes, ActiveSupport wraps the JSON library, a standard Ruby Gem which you can use in any Ruby project. However, ActiveSupport goes beyond the boundary of a simple wrapper: it provides a JSON definition for the most part of the Ruby objects making JSON an effective full drop-in replacement for YAML. I’m going to talk about this later in this article.

ActiveSupport::JSON

As I mentioned before, ActiveSupport::JSON relies on the JSON Gem thus you need to have both libraries installed on your system. If you installed the Ruby on Rails framework, then you already have everything you need to start working with JSON.

The module provides a super-simple API composed by two methods:

  • ActiveSupport::JSON.encode(object)
  • ActiveSupport::JSON.decode(string)

ActiveSupport::JSON.encode(object) takes a Ruby object as value and returns a JSON-encoded string. On the opposite, ActiveSupport::JSON.decode(string) takes a JSON-encoded string and returns the corresponding Ruby object.

Here’s a few examples

j = ActiveSupport::JSON
ruby-1.8.7-p249 > j.encode(23)
# => "23"
j.encode("A string")
# => "A string"
j.encode({ :color => ["red", "green", "jellow"] })
# => {"color":["red","green","jellow"]}
j.encode({ :color => ["red", "green", "jellow"], :date => Time.now })
# => {"color":["red","green","jellow"],"date":"2010-04-29T00:28:56+02:00"}

j.decode(j.encode({ :color => ["red", "green", "jellow"], :date => Time.now }))
# => {"date"=>"2010-04-29T00:25:52+02:00", "color"=>["red", "green", "jellow"]}

As you can see, the usage is really straightforward and the JSON-encoded result size is smaller compared to the YAML and XML counterparts.

v = { :color => ["red", "green", "jellow"], :date => Time.now }
# => {:color=>["red", "green", "jellow"], :date=>Thu Apr 29 00:28:56 +0200 2010} 

ActiveSupport::JSON.encode(v)
# 69 bytes
# => {"color":["red","green","jellow"],"date":"2010-04-29T00:28:56+02:00"}

YAML.dump(v)
# 84 bytes
# ---
# :color:
# - "red"
# - "green"
# - "jellow"
# :date: 2010-04-29 00:28:56.827076 +02:00

ActiveSupport::JSON vs JSON

At the beginning of the article, I said ActiveSupport::JSON is something more than a mere JSON wrapper. Now it’s the time to explain that statement.

The JSON format natively supports only a limited subset of variable types such as String, Number, Array and Hash. Easy to understand, being a language-agnostic format, it doesn’t support complex or ruby-specific objects such as Object, Exception or Range. For this reason, JSON library delegates to each class the implementation of the JSON representation of the object using the to_json method.
Similar to other standard transformation methods such as to_s or to_f, to_json is supposed to return a JSON-compatible representation.

While the JSON library now ships with a number of prepackaged definitions, by default it doesn’t support most of the standard Ruby objects. Also it doesn’t support the serialization of ActiveRecord objects and, working with Rails projects and database records, this might be a huge limitation.

ActiveSupport::JSON solves this problem and provides a predefined to_json implementation for the most part of Ruby/Rails objects. It also defines a simple Object#to_json making virtually every Ruby object JSON-compatible.

As of ActiveSupport 2.3.5, the following classes are supported: String, Symbol, Date, Time, DateTime, Enumerable, Array, Hash, FalseClass, TrueClass, NilClass, Numeric, Float, Integer, Regexp, and Object. You can find them in the lib/active_support/json/encoders folder. The ActiveRecord serialization/deserialization strategy is defined in the ActiveRecord library in lib/active_record/serializers/json_serializer.rb:

def to_json(options = {})
  super
end

def as_json(options = nil) #:nodoc:
  hash = Serializer.new(self, options).serializable_record
  hash = { self.class.model_name.element => hash } if include_root_in_json
  hash
end

def from_json(json)
  self.attributes = ActiveSupport::JSON.decode(json)
  self
end

Compared with JSON Gem, ActiveSupport::JSON is the solution to the following Alan’s statement:

There is bad news of course, in that your objects won’t automagically be converted to JSON, unless all you’re using is hashes, arrays and primitives. You need to do a little bit of work to make sure your custom object is serializable. Let’s make one of the classes we introduced previously serializable using JSON.

As a side note, it also provides some additional features such as an interchangeable encoding/decoding backend.

One final note about Rails 3 (and ActiveSupport 3): the ActiveSupport::JSON module has been recently modified in Rails 3. Objects without a native JSON representation are now encoded using to_hash or to_s instead of instance_values.

  1. Understanding Ruby and Rails: Reading Source Code
  2. Understanding Ruby and Rails: Lazy load hooks
  3. Understanding Ruby and Rails: Rescuable and rescue_from
  4. Understanding Ruby and Rails: Delegate
  5. Understanding Ruby and Rails: Benchmarking your Ruby scripts

Filed in Programming • Tags: , , , , ,

Comments

S. M. Sohan says:

Your post has been linked at the Drink Rails blog.

UMA MAHESH says:

really a good post a clean explanation about the JSON.

Thank You,
Uma.

daniel Engfeldt says:

Hi, very nice blog post!

Tried to ActiveSupport::JSON.decode(json) my json containing a encodede Time.now representation but the result is always a string-object containing the time.
Shouldnt the decoded object be a Time-object after decoding or is the encoders included with activesupport just doing the work when encoding content and not back again when decoding?

Unfortunately, JSON can only decode to strings. This is because it doesn’t have any knowledge to understand whether Mon May 03 10:08:52 +0200 2010 is a Date, a Time or a String containing Mon May 03 10:08:52 +0200 2010.

If you want to restore the original object state, you can follow the ActiveRecord example and create a from_json method which takes a JSON input, decodes it and restores the original object.

850mph says:

Small typo in the following area:

The module provides a super-simple API composed by two methods:

* ActiveSupport::JSON.encode(object)
* ActiveSupport::JSON.encode(string)

Should read:

The module provides a super-simple API composed by two methods:

* ActiveSupport::JSON.encode(object)
* ActiveSupport::JSON.decode(string)

thats “ACTIVESUPPORT::JSON.DECODE(STRING)”

Fixed, thank you.

Adam says:

You missed the typo of “ActiveSupport::JSON.encode(string)” in the following paragraph as well ;)

This article and your more recent one on UJS callbacks have been both very helpful! Keep it up.

Thank you Adam, fixed.

comrade says:

The name is Alan Skorkin not Alan Snorkin please correct

oo says:

What’s the type of ActiveSupport::JSON.decode returned??

Can ActiveSupport::JSON.decode parse json to hash??

It returns String, Array, Hash or Fixnum depending on the input JSON string.

Hi Simone,

Thanks for the very helpful article. Do you happen to know how to tell rails to use JSON rather than YAML when persisting a serialized object to a database large text field? Thanks,

-Chris

[...] knows how to convert ActiveRecord objects (and more) to JSON. Simone Carletti explains how this differs from the standard lib. (OK, now I really need to freshen up my “displaying code pretty” [...]

Colin Adams says:

Thanks Simone – Great article. I was needing to generate JSON from our Rails app to pass to a Java app, and your examples came in really handy. Much appreciated.

Thom Parkin says:

Great article. It helped me with a specific problem in a Rails project.
But I need to ask, “What color is JELLOW ?!”

Pierre says:

Many thanks, indeed. I feel like I finally understood what JSON is…

Add a Comment




Follow Me
    Random Quote