Forum: Ruby Best practice to create a JSON string of one object containing an array of other objects using Ruby?

Ee1967c31c30b0f7337decfb5569cc68?d=identicon&s=25 Matthias S. (matthias_s)
on 2011-02-09 16:42
What is a good practice to create a JSON string of one object (object of
class A) containing an array of objects (objects of class B)? I am
particularly interessted in the implementation of class's A to_json
method.

Assuming class A looks as follows:

class A
  attr_accessor :items
  def initialize()
    @items = Array.new
  end
  def to_json(*a)
    ?SECRET OF THE DAY?
  end
end

and class B:

class B
  def to_json(*a)
    {"class B" => "class B"}.to_json(*a)
  end
end

The best solution I got so far is:

def to_json(*a)
  json = Array.new
  @items.each do |item|
    json << item.to_json(*a)
  end
  {"class A" => json}.to_json(*a)
end

Assuming there is only one item in array of an object of class A, the
resulting JSON string looks as follows:

{"class A":["{\"class B\":\"class B\"}"]}

I also tried:

def to_json(*a)
  {"class A" => @items}.to_json(*a)
end

But it results in IOError exception in class A, saying to_json "not
opened for reading"...

I am sure we can do better?
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2011-02-09 20:07
> But it results in IOError exception in class A, saying to_json "not
> opened for reading"...
>
> I am sure we can do better?

I think it would be much better to make the class an attribute of the
JSON object, not build a new JSON object of {class => {attributes}}

If you look at the tests/ directory of the json or json_pure gem, you'll
see that they use 'json_class' for this purpose. e.g.

  class A
    def initialize(a)
      @a = a
    end

    attr_reader :a

    def ==(other)
      a == other.a
    end

    def self.json_create(object)
      new(*object['args'])
    end

    def to_json(*args)
      {
        'json_class'  => self.class.name,
        'args'        => [ @a ],
      }.to_json(*args)
    end
  end

The use of the 'args' element (being the array of arguments to #new) is
fairly ugly. I would rather expose the instance variables as members of
the JSON object:

  class A
    def initialize(a)
      @a = a
    end

    attr_reader :a

    def self.json_create(object)
      new(object['a'])
    end

    def to_json(*args)
      {
        'json_class'  => self.class.name,
        'a'           => @a,
      }.to_json(*args)
    end
  end

  a1 = A.new([123, 456])
  puts a1.inspect
  str = a1.to_json
  puts str
  a2 = JSON.parse(str)
  puts a2.inspect

HTH,

Brian.
Ee1967c31c30b0f7337decfb5569cc68?d=identicon&s=25 Matthias S. (matthias_s)
on 2011-02-09 21:40
@Brian: Both approaches you proposed result in IOError exception in
class A, saying to_json "not opened for reading".

Besides adding a class attribute, both approaches are actually similar
to the second I mentioned...
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2011-02-09 21:57
Matthias S. wrote in post #980645:
> @Brian: Both approaches you proposed result in IOError exception in
> class A, saying to_json "not opened for reading".

Can you paste the *exact* exception you see, and the exact code which
triggers it? Plus details of the platform you are on.

It works for me:

$ ruby -v
ruby 1.8.7 (2010-06-23 patchlevel 299) [x86_64-linux]
$ ruby ert.rb
#<A:0x7f23333ad7e0 @a=[123, 456]>
{"a":[123,456],"json_class":"A"}
#<A:0x7f23333a7598 @a=[123, 456]>

That's running the code I posted, just with the addition of

require 'rubygems'
require 'json'       # also works with require 'json/pure'

at the top. I have the json-1.4.3 gem installed, and json_pure-1.4.3.

Regards,

Brian.
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2011-02-09 22:01
And here is a working example for your code where class A includes
instances of class B:

$ cat ert2.rb
require 'rubygems'
require 'json'

class A
  attr_accessor :items
  def initialize()
    @items = Array.new
  end
  def to_json(*a)
    {
      "json_class" => self.class.name,
      "items" => @items,
    }.to_json(*a)
  end
end

class B
  def to_json(*a)
    {
      "json_class" => self.class.name,
    }.to_json(*a)
  end
end

a = A.new
a.items << B.new
a.items << B.new
puts a.to_json

$ ruby ert2.rb
{"items":[{"json_class":"B"},{"json_class":"B"}],"json_class":"A"}
Ee1967c31c30b0f7337decfb5569cc68?d=identicon&s=25 Matthias S. (matthias_s)
on 2011-02-09 22:44
Running "ert2.rb" in console worked as expected.

I want to use to_json capability within a rails project. In my project,
I created class A and B as well as a controller to call the to_json
method. I got:

{"json_class":"A","items":[{},{}]}

After that, I reinstalled json and json_pure, using version 1.5.1 of
both now.

Now it works as expected... @Thanks to Brian

Rails 3.0.3
ruby 1.9.2p136 (2010-12-25 revision 30365) [i386-darwin9.8.0]
json 1.5.1
json_pure 1.5.1

So, reinstalling/upgrading json libs might solve the problem...
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.