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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With