Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep Convert OpenStruct to JSON

Tags:

ruby

I have an OpenStruct that is nested with many other OpenStructs. What's the best way to deeply convert them all to JSON?

Ideally:

x = OpenStruct.new
x.y = OpenStruct.new
x.y.z = OpenStruct.new
z = 'hello'

x.to_json
// {y: z: 'hello'}

Reality

{ <OpenStruct= ....> }
like image 258
Breedly Avatar asked Oct 13 '15 00:10

Breedly


5 Answers

There is no default methods to accomplish such task because the built-in #to_hash returns the Hash representation but it doesn't deep converts the values.

If a value is an OpenStruct, it's returned as such and it's not converted into an Hash.

However, this is not that complicated to solve. You can create a method that traverses each key/value in an OpenStruct instance (e.g. using each_pair), recursively descends into the nested OpenStructs if the value is an OpenStruct and returns an Hash of just Ruby basic types.

Such Hash can then easily be serialized using either .to_json or JSON.dump(hash).

This is a very quick example, with an update from @Yuval Rimar for arrays of OpenStructs:

def openstruct_to_hash(object, hash = {})
  case object
  when OpenStruct then
    object.each_pair do |key, value|
    hash[key] = openstruct_to_hash(value)
    end
    hash
  when Array then
    object.map { |v| openstruct_to_hash(v) }
  else object
  end
end

openstruct_to_hash(OpenStruct.new(foo: 1, bar: OpenStruct.new(baz: 2)))
# => {:foo=>1, :bar=>{:baz=>2}}
like image 134
Simone Carletti Avatar answered Oct 16 '22 21:10

Simone Carletti


Fixes to above solution to handle arrays

def open_struct_to_hash(object, hash = {})
  object.each_pair do |key, value|
    hash[key] = case value
                  when OpenStruct then open_struct_to_hash(value)
                  when Array then value.map { |v| open_struct_to_hash(v) }
                  else value
                end
  end
  hash
end
like image 20
Lance Gatlin Avatar answered Oct 16 '22 21:10

Lance Gatlin


Here's yet another approach, modified from lancegatlin's answer. Also adding the method to the OpenStruct class itself.

class OpenStruct
  def deep_to_h
    each_pair.map do |key, value|
      [
        key,
        case value
          when OpenStruct then value.deep_to_h
          when Array then value.map {|el| el === OpenStruct ? el.deep_to_h : el}
          else value
        end
      ]
    end.to_h
  end
like image 28
lunarfyre Avatar answered Oct 16 '22 21:10

lunarfyre


Same function that can accept arrays as an input too

  def openstruct_to_hash(object, hash = {})
    case object
    when OpenStruct then
      object.each_pair do |key, value|
        hash[key] = openstruct_to_hash(value)
      end
      hash
    when Array then
      object.map { |v| openstruct_to_hash(v) }
    else object
    end
  end
like image 34
Yuval Rimar Avatar answered Oct 16 '22 22:10

Yuval Rimar


in initializers/open_struct.rb:

require 'ostruct'

# Because @table is a instance variable of OpenStruct and Object#as_json returns Hash of instance variables.
class OpenStruct
  def as_json(options = nil)
    @table.as_json(options)
  end
end

Usage:

OpenStruct.new({ a: { b: 123 } }).as_json

# Result
{
    "a" => {
        "b" => 123
    }
}

Edit:
This seems to do almost the same thing (notice the keys are symbols instead of strings)

OpenStruct.new({ a: { b: 123 } }).marshal_dump

# Result
{
    :a => {
        :b => 123
    }
}
like image 3
Frexuz Avatar answered Oct 16 '22 22:10

Frexuz