Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursively dump Ruby object to json

Tags:

json

ruby

I have a hash that I need to dump to json, but one of its values is an object:

hash = { a: my_object }

If I try to dump this with something like MultiJson.dump(hash), it will only serialize the top-level hash, and not any deeper, so what I end up with is

'{"a": #<...>}'

This happens even if the object has to_json, to_hash, etc. methods already, and MultiJson.dump(my_object) works just fine.

This seems like something JSON libraries should do, but I guess they don't. Why not? Is there a programmatic reason they shouldn't/cant? Or am I just missing something?

Edit:

A bit more searching around -- I must have messed up something in my initial tests. Oj can dump json recursively with the :compat option:

Oj.dump(my_object, mode: :compat)

MultiJson using Oj passes this option in by default, so that will work too. Although, according to Oj documentation, I'm not sure why this works. It works even without any as_hash or to_json methods.

like image 251
Elmer Avatar asked Sep 16 '26 13:09

Elmer


1 Answers

Generally I do one of two things:

Load JSON's core extensions:

require 'json/add/core'

I don't remember why "add/core" helped over the normal require 'json' but it did.

Or I add a to_hash or to_array method to custom classes and break them down into a Hash or Array that can then be passed off to the JSON class to serialize. Or I add a to_json method and create a Hash or Array and then to_json it, returning the serialized version:

class Foo
  def initialize
    @bar = 1
    @baz = [1, 2, 3]
    @hub = {'a' => 0, 'b' => 2}
  end

  def to_h
    {
      'bar' => @bar,
      'baz' => @baz,
      'hub' => @hub
    }
  end

  def to_json
    to_h.to_json
  end
end

require 'json'

Foo.new.to_json # => "{\"bar\":1,\"baz\":[1,2,3],\"hub\":{\"a\":0,\"b\":2}}"

Add code to do a from_json to a String and you can send and receive your object as JSON. Or add that capability to your initialize method so if it sees a String for a parameter it tries to convert that string back to a Ruby object and then populate the values:

class Foo
  def initialize(str = nil)
    if str && String === str
      obj = JSON[str]
      @bar, @baz, @hub = obj.values_at('bar', 'baz', 'hub')
    else
      @bar = 1
      @baz = [1, 2, 3]
      @hub = {'a' => 0, 'b' => 2}
    end
  end

  def to_h
    {
      'bar' => @bar,
      'baz' => @baz,
      'hub' => @hub
    }
  end

  def to_json
    to_h.to_json
  end
end

require 'json'

json_stream = Foo.new.to_json # !> assigned but unused variable - json_stream
new_foo = Foo.new('{"bar":3,"baz":4,"hub":{"x":8,"y":9}}')
# => #<Foo:0x007f9ed4054d60 @bar=3, @baz=4, @hub={"x"=>8, "y"=>9}>

As long as JSON knows how to unravel a particular object, it'll do a to_json on it without trouble. For those types that it doesn't know, it's easy to give it a little help.

like image 110
the Tin Man Avatar answered Sep 18 '26 06:09

the Tin Man



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!