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

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?

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.

@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…

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.

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…

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”}