6

I have an OpenStruct object and needs to convert to JSON data.

Sample Hash (from RSPEC helper):

def test_order
 {
   "id": 505311428702,
   "email": "test@gmail.com",
   "closed_at": "",
   "discount_codes": {
      "id": 507328175,
      "text": "test"
   }
 }
end

I'm using, below function for recursive:

def to_recursive_ostruct(hash)
  OpenStruct.new(hash.each_with_object({}) do |(key, val), memo|
    memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
  end)
end

For ex to_recursive_ostruct(test_order), will return:

<OpenStruct id=505311428702, email="test@gmail.com", closed_at="", ...>

Once converted, using OpenStructObject.marshal_dump:

{
:id=>505311428702, :email=>"test@gmail.com", :closed_at=>"", 

discount_codes=>#<OpenStruct id=507328175, text= "test">}
}

OpenStructObject.marshal_dump gives me the right data in first level,

I want also the nested data to beconverted.

What I really need is like:

{:id=>505311428702, :email=>"test@gmail.com", :closed_at=>"", :discount_codes=>{:id=>507328175, :text=> "test"} }

Please help, thanks in advance.

aldrien.h
  • 3,437
  • 2
  • 30
  • 52
  • I don't understand how `JSON.parse(openstruct_object.to_json)` gives you your output. The code is wrong, and the output is not what it should be – Andrey Deineko Aug 22 '18 at 07:01
  • 1
    [Here](https://gist.github.com/3limin4t0r/a29771d2f2d4d31e757cc7e9d5f1ee1f) is some brainstorming about your issue. – 3limin4t0r Aug 22 '18 at 10:15

5 Answers5

4

Building on work done by @Andrey-Deineko, @owyongsk, @aldrien.h here is a converter that handles arrays. While the OP wasn't looking this in particular, others may find it helpful.


def openstruct_to_h(object)
   object.to_h.transform_values do |value|
      value.is_a?(OpenStruct) ? openstruct_to_h(value) : value.is_a?(Array) ? value.map{|v| v.is_a?(String) ? v : openstruct_to_h(v) } : value
   end
end

# Example usage

open_struct_object = OpenStruct.new(paramtest: "ok", array_test: [OpenStruct.new(array_one: true), OpenStruct.new(array_two: false, nested_os: OpenStruct.new(works: 'maybe'))], os_test: OpenStruct.new(testy: "yup")) 

openstruct_to_h(open_struct_object)

=> {:paramtest=>"ok", :array_test=>[{:array_one=>true}, {:array_two=>false, :nested_os=>{:works=>"maybe"}}], :os_test=>{:testy=>"yup"}}
Tayden
  • 276
  • 2
  • 8
2

Check out docs.

You can use OpenStruct#marshal_dump:

openstruct_object.marshal_dump

OpenStruct#to_h will work, too:

openstruct_object.to_h

You can convert your object to hash and then hash to JSON:

openstruct_object.to_h.to_json

But it looks like what you want is a Hash object, not JSON object.

Andrey Deineko
  • 51,333
  • 10
  • 112
  • 145
  • 1
    Wow. I didn’t know about [`OpenStruct#marshal_dump`](https://ruby-doc.org/stdlib/libdoc/ostruct/rdoc/OpenStruct.html#method-i-marshal_dump). – Aleksei Matiushkin Aug 22 '18 at 06:46
2

On Ruby 2.4+, you can use transform_values together with a recursive function in a monkey patch.

class OpenStruct
  def deep_to_h
    to_h.transform_values do |v|
      v.is_a?(OpenStruct) ? v.deep_to_h : v
    end
  end
end

Or if you don't want to monkey patch

def deep_to_h(obj)
  obj.to_h.transform_values do |v|
    v.is_a?(OpenStruct) ? deep_to_h(v) : v
  end
end
owyongsk
  • 2,349
  • 1
  • 19
  • 21
1

To convert your deep openstruct to hash you could go with something along these lines:

def deep_openstruct_to_hash(object)
  object.each_pair.with_object({}) do |(key, value), hash|
    hash[key] = value.is_a?(OpenStruct) ? deep_openstruct_to_hash(value) : value
  end
end

Then:

openstruct_object = to_recursive_ostruct(test_order)
#=> #<OpenStruct id=505311428702, email="test@gmail.com", closed_at="", discount_codes=#<OpenStruct id=507328175, text="test">>
deep_openstruct_to_hash(openstruct_object)
# {
#   :id=>505311428702,
#   :email=>"test@gmail.com",
#   :closed_at=>"",
#   :discount_codes=>{
#     :id=>507328175,
#     :text=>"test"
#   }
# }
Andrey Deineko
  • 51,333
  • 10
  • 112
  • 145
  • 1
    `deep_to_h = ->(hash) { hash.to_h.map { |k, v| [k, v.respond_to?(:to_h) ? deep_to_h.(v) : v] }.to_h }` and use it as `deep_to_h.(test_order)` makes the approach more generic. – Aleksei Matiushkin Aug 22 '18 at 09:02
  • @mudasobwa However if one of the values is an array it could also be an issue since `[1, 2, 3].to_h #=> TypeError: wrong element type Integer at 0 (expected array)`. – 3limin4t0r Aug 22 '18 at 10:13
  • @JohanWentholt true that. One might explicitly exclude arrays then. – Aleksei Matiushkin Aug 22 '18 at 10:16
  • @Andrey if i have ]> deep_openstruct_to_hash cannot convert (discount_codes) if value is Array, any idea? Thank you. – aldrien.h Aug 24 '18 at 07:57
0

Thanks also to this gist: Can converted array of hash as well.

def recursive_ostruct(object)
  case object
  when Hash
    hash = {}; object.each{|k,v| hash[k] = recursive_ostruct(v)}
    OpenStruct.new(hash)
  when Array
    object.map {|e| recursive_ostruct(e) }
  else
    object
  end
end
aldrien.h
  • 3,437
  • 2
  • 30
  • 52