Is there a nice way to transform an instance of a HashWithIndifferentAccess
(with nested instances of class HashWithIndifferentAccess
) to an instance of a Hash (with nested instances of class Hash
)?
It seems easy to convert a nested Hash
to a nested HashWithIndifferentAccess
. Just use the with_indifferent_access
method that ActiveSupport provides. This converts all hashes, no matter how deeply nested.
hash = { late: { package: 2, trial: 100, penalty: { amount: 1 } },
no_show: { package: 1, trial: 100, penalty: { amount: 2 } } }
hash_wid = hash.with_indifferent_access
hash_wid.class
# ActiveSupport::HashWithIndifferentAccess #great
hash_wid [:no_show][:penalty].class
# ActiveSupport::HashWithIndifferentAccess #great
The reverse seems not so easy:
hash = hash_wid.to_h
hash.class
# Hash # OK
hash[:no_show][:penalty].class
# ActiveSupport::HashWithIndifferentAccess # want this to be Hash
Hash#to_h
method only converts the top level hash, not the nested hashes.
I tried the (Rails/ActiveSupport) deep_transform_values!
method that extends the Hash
class:
hash_wid.deep_transform_values! do |value|
value.class == HashWithIndifferentAccess ? value.to_h : value
end
hash_wid.class
# ActiveSupport::HashWithIndifferentAccess # want this to be Hash
hash_wid[:no_show][:penalty].class
# ActiveSupport::HashWithIndifferentAccess # want this to be Hash
But looking to the source code of the deep_transform_values!
method (and transform_values!
method that it depends upon), these can transform hashes of class Hash
, but not hashes of class HashWithIndifferentAccess
.
So is there a nice way to transform a nested HashWithIndifferentAccess
to a nested Hash
?
Thanks
Daniel
CodePudding user response:
Your example indicates that you are using keys and values whose types are compatible with JSON. If your hash can be converted to JSON then a simple way to do it is:
hash = JSON.load(JSON.dump({ foo: { bar: 'baz' }.with_indifferent_access }.with_indifferent_access))
=> {"foo"=>{"bar"=>"baz"}}
hash.class
=> Hash
hash['foo'].class
=> Hash
deep_transform_values
won't work in your case because of how that method is written:
def _deep_transform_values_in_object(object, &block)
case object
when Hash
object.transform_values { |value| _deep_transform_values_in_object(value, &block) }
when Array
object.map { |e| _deep_transform_values_in_object(e, &block) }
else
yield(object)
end
end
If your object is a Hash (or Array) then the method calls itself recursively until it finds a non-Hash and non-Array value onto which it can apply the transformation. It should be pretty trivial to write your own implementation using that example though:
def _deep_transform_values_in_object(object, &block)
case object
when Hash
# something less ugly than but similar to this
object = object.to_h if object.is_a?(ActiveSupport::HashWithIndifferentAccess)
object.transform_values { |value| _deep_transform_values_in_object(value, &block) }
when Array
object.map { |e| _deep_transform_values_in_object(e, &block) }
else
yield(object)
end
end