Ruby objects and JSON serialization (without Rails)

I'm trying to understand the JSON serialization landscape in Ruby. I'm new to Ruby.

Is there any good JSON serialization options if you are not working with Rails?

That seems to be where this answer goes (to Rails) How to convert a Ruby object to JSON

The json gem seems to make it look like you have to write your own to_json method. I haven't been able to get to_json to work with arrays and hashes (documentation says it works with these) Is there a reason the json gem doesn't just reflect over the object and use a default serialization strategy? Isn't this how to_yaml works (guessing here)


Solution 1:

For the JSON library to be available, you may have to install libjson-ruby from your package manager.

To use the 'json' library:

require 'json'

To convert an object to JSON (these 3 ways are equivalent):

JSON.dump object #returns a JSON string
JSON.generate object #returns a JSON string
object.to_json #returns a JSON string

To convert JSON text to an object (these 2 ways are equivalent):

JSON.load string #returns an object
JSON.parse string #returns an object

It will be a bit more difficult for objects from your own classes. For the following class, to_json will produce something like "\"#<A:0xb76e5728>\"".

class A
    def initialize a=[1,2,3], b='hello'
        @a = a
        @b = b
    end
end

This probably isn't desirable. To effectively serialise your object as JSON, you should create your own to_json method. To go with this, a from_json class method would be useful. You could extend your class like so:

class A
    def to_json
        {'a' => @a, 'b' => @b}.to_json
    end
    def self.from_json string
        data = JSON.load string
        self.new data['a'], data['b']
    end
end

You could automate this by inheriting from a 'JSONable' class:

class JSONable
    def to_json
        hash = {}
        self.instance_variables.each do |var|
            hash[var] = self.instance_variable_get var
        end
        hash.to_json
    end
    def from_json! string
        JSON.load(string).each do |var, val|
            self.instance_variable_set var, val
        end
    end
end

Then you can use object.to_json to serialise to JSON and object.from_json! string to copy the saved state that was saved as the JSON string to the object.

Solution 2:

Check out Oj. There are gotchas when it comes to converting any old object to JSON, but Oj can do it.

require 'oj'

class A
    def initialize a=[1,2,3], b='hello'
        @a = a
        @b = b
    end
end

a = A.new
puts Oj::dump a, :indent => 2

This outputs:

{
  "^o":"A",
  "a":[
    1,
    2,
    3
  ],
 "b":"hello"
}

Note that ^o is used to designate the object's class, and is there to aid deserialization. To omit ^o, use :compat mode:

puts Oj::dump a, :indent => 2, :mode => :compat

Output:

{
  "a":[
    1,
    2,
    3
  ],
  "b":"hello"
}

Solution 3:

If rendering performance is critical, you might also want to look at yajl-ruby, which is a binding to the C yajl library. The serialization API for that one looks like:

require 'yajl'
Yajl::Encoder.encode({"foo" => "bar"}) #=> "{\"foo\":\"bar\"}"

Solution 4:

What version of Ruby are you using? ruby -v will tell you.

If it's 1.9.2, JSON is included in the standard library.

If you're on 1.8.something then do gem install json and it'll install. Then, in your code do:

require 'rubygems'
require 'json'

Then append to_json to an object and you're good to go:

asdf = {'a' => 'b'} #=> {"a"=>"b"}
asdf.to_json #=> "{"a":"b"}"

Solution 5:

Since I searched a lot myself to serialize a Ruby Object to json:

require 'json'

class User
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  def as_json(options={})
    {
      name: @name,
      age: @age
    }
  end

  def to_json(*options)
    as_json(*options).to_json(*options)
  end
end

user = User.new("Foo Bar", 42)
puts user.to_json #=> {"name":"Foo Bar","age":42}