Home > OS >  Convert nested HashWithIndifferentAccess to nested Hash
Convert nested HashWithIndifferentAccess to nested Hash

Time:11-17

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
  • Related