Deep Convert OpenStruct to JSON
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 OpenStruct
s 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}}
How to convert recursive/nested OpenStruct Object to Hash
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.
Convert Hash to OpenStruct recursively
personally I use the recursive-open-struct
gem - it's then as simple as RecursiveOpenStruct.new(<nested_hash>)
But for the sake of recursion practice, I'll show you a fresh solution:
require 'ostruct'
def to_recursive_ostruct(hash)
result = hash.each_with_object({}) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
OpenStruct.new(result)
end
puts to_recursive_ostruct(a: { b: 1}).a.b
# => 1
edit
Weihang Jian showed a slight improvement to this here https://stackoverflow.com/a/69311716/2981429
def to_recursive_ostruct(hash)
hash.each_with_object(OpenStruct.new) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
end
Also see https://stackoverflow.com/a/63264908/2981429 which shows how to handle arrays
note
the reason this is better than the JSON-based solutions is because you can lose some data when you convert to JSON. For example if you convert a Time object to JSON and then parse it, it will be a string. There are many other examples of this:
class Foo; end
JSON.parse({obj: Foo.new}.to_json)["obj"]
# => "#<Foo:0x00007fc8720198b0>"
yeah ... not super useful. You've completely lost your reference to the actual instance.
Construct nested OpenStruct object
This method is rude method but works,
require 'ostruct'
require 'json'
# Data in hash
data = {"names" => {"first_name" => "Bob"}}
result = JSON.parse(data.to_json, object_class: OpenStruct)
And another method is adding method to Hash class itself,
class Hash
def to_openstruct
JSON.parse to_json, object_class: OpenStruct
end
end
Using above method you can convert your hash to openstruct
data = {"names" => {"first_name" => "Bob"}}
data.to_openstruct
Hyphens in serialized JSON OpenStruct
You can use square brackets like you would with a hash:
obj["10-20"]
#=> 0
Of course, if most of the keys are not valid method names anyway, then you might as well just use a hash and not bother with an OpenStruct
.
Related documentation: OpenStruct#[]
Ruby: Convert nested hash to object?
You need to add recursivity:
class Hashit
def initialize(hash)
hash.each do |k,v|
self.instance_variable_set("@#{k}", v.is_a?(Hash) ? Hashit.new(v) : v)
self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})
self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})
end
end
end
h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
# => #<Hashit:0x007fa6029f4f70 @a="123r", @b=#<Hashit:0x007fa6029f4d18 @c="sdvs">>
Ruby: Convert nested hash to object?
You need to add recursivity:
class Hashit
def initialize(hash)
hash.each do |k,v|
self.instance_variable_set("@#{k}", v.is_a?(Hash) ? Hashit.new(v) : v)
self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})
self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})
end
end
end
h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
# => #<Hashit:0x007fa6029f4f70 @a="123r", @b=#<Hashit:0x007fa6029f4d18 @c="sdvs">>
Related Topics
Instance_Eval Does Not Work with Do/End Block, Only with {}-Blocks
Config Undefined in Environment Specific Configuration Files
Integrate Shoes into Aptana Studio Radrails
Why Is Autoload Failing to Load Files for Gems
Uploading New Products with Multiple Variant Options
How to Mix Required Argument and Optional Arguments in Ruby
Regular Expression to Match Only the First File in a Rar File Set
Share Session Between Two Rails4 Applications
Incompatible Character Encoding in Rails - How to Just Fail/Skip Sensibly
Splitting a String into Words and Punctuation with Ruby
Display Result Data Without Page Refresh on Form Submission in Ruby on Rails
How to 'Join' an Array Adding to the Beginning of the Resulting String the First Character to Join
Trouble on Eager Loading "Second Degree" Associated Objects