Generate Nested Hashes from Strings and Deep Merging in Ruby

Generate nested hashes from strings and deep merging in ruby

To generate nested hash:

hash = {}

"one.two.three".split('.').reduce(hash) { |h,m| h[m] = {} }

puts hash #=> {"one"=>{"two"=>{"three"=>{}}}}

If you don't have rails installed then install activesupport gem:

gem install activesupport

Then include it into your file:

require 'active_support/core_ext/hash/deep_merge'

hash = {
"one" => {
"two" => {
"three" => {}
}
}
}.deep_merge(another_hash)

The access to the internals would be:

hash['one']['two']['three'] #=> {}

Merge nested hash without overwritting in Ruby

For Rails there is the deep_merge function for ActiveSupport that does exactly what you ask for.

You can implement the same for yourself as follows:

class ::Hash
def deep_merge(second)
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
self.merge(second, &merger)
end
end

Now,

h1 = {"a"=>"text"}
h2 = {"b"=>{"x"=>"hola"}}
h3 = {"b"=>{"y"=>"pto"}}
h1.deep_merge(h2).deep_merge(h3)
# => {"a"=>"text", "b"=>{"x"=>"hola", "y"=>"pto"}}

Merging nested hash with deep_merge using the same key

If you are using this [deep_merge][1] or [deep_merge!][2] method, then try following

collection.deep_merge(new_collection) { |key, this_val, other_val| 
this_val.class == String ? this_val : this_val + other_val
}

Note:- We have to add this_val.class == String otherwise we'll get provider=>"totaltotal" & also assuming each hash key will have value of same data type.

[13] pry(main)> collection = {:foo=>{:amount_gross=>1886.0}, :bar=>{:amount_gross=>184.0}, :total=>{:provider=>"total", :amount_gross=>2070.0}}
=> {:foo=>{:amount_gross=>1886.0}, :bar=>{:amount_gross=>184.0}, :total=>{:provider=>"total", :amount_gross=>2070.0}}
[14] pry(main)> new_collection = {:foo=>{:amount_gross=>100.0}, :bar=>{:amount_gross=>100.0}, :total=>{:provider=>"total", :amount_gross=>200.0}}
=> {:foo=>{:amount_gross=>100.0}, :bar=>{:amount_gross=>100.0}, :total=>{:provider=>"total", :amount_gross=>200.0}}
[15] pry(main)> collection.deep_merge!(new_collection) { |key, this_val, other_val| this_val.class == String ? this_val : this_val + other_val}
=> {:foo=>{:amount_gross=>1986.0}, :bar=>{:amount_gross=>284.0}, :total=>{:provider=>"total", :amount_gross=>2270.0}}

Ruby: Nested hash loses keys and values on merging with another nested hash

The way Hash#merge works is consistent: if the key exists in both, then the key value of the merged-in hash replaces the the first one in all cases. You'll need a recursive merge.

There is a deep_merge available in Rails. But if you're not using Rails, or if it doesn't suit your requirements, you can roll your own fairly easily.

Hash#merge does support a block which can help:

h1.merge(h2) { |k, old, new| (old.instance_of?(Hash) && new.instance_of?(Hash)) ?
old.merge(new) : new }

This will work if you just have one layer deep of embedded hashes. If you have an arbitrary depth of nested hashes, you can monkey patch Hash with a deep_merge:

class Hash
def deep_merge(h)
self.merge(h) { |k, old, new| (old.instance_of?(Hash) && new.instance_of?(Hash)) ?
old.deep_merge(new) : new }
end
end

Or something like that... :) This will recursively merge if there is a hash in both cases, otherwise, it replaces as usual. You can modify it to your taste.

Trying this out for your case:

2.6.1 :008 > class Hash
2.6.1 :009?> def deep_merge(h)
2.6.1 :010?> self.merge(h) { |k, old, new| (old.instance_of?(Hash) && new.instance_of?(Hash)) ?
2.6.1 :011 > old.deep_merge(new) : new }
2.6.1 :012?> end
2.6.1 :013?> end
=> :deep_merge
2.6.1 :014 > h1 = {"a"=>{"x"=>"1","y"=>"2"}, "b"=>"z"}
=> {"a"=>{"x"=>"1", "y"=>"2"}, "b"=>"z"}
2.6.1 :015 > h2 = {"a"=>{"x"=>"3"}, "b"=>"q"}
=> {"a"=>{"x"=>"3"}, "b"=>"q"}
2.6.1 :016 > h1.deep_merge(h2)
=> {"a"=>{"x"=>"3", "y"=>"2"}, "b"=>"q"}
2.6.1 :017 >

hash deep_merge with arrays

If h1 and h2 are the two hashes you wish to merge, and h3 is the desired result, I think the following should work:

@merge_key1, @merge_key2 = "key-one", "key-two"

def merge_em(g1, g2)
case g1
when Array
g1.size.times {|i| merge_em(g1[i], g2[i])}
when Hash
g1.keys.each do |k|
v = g1[k]
if (Hash === v || (Array === v && !v.empty?))
merge_em(v, g2[k])
else
g1[k] = {@merge_key1 => v, @merge_key2 => g2[k]}
end
end
end
end

h = Marshal.load(Marshal.dump(h1))
merge_em(h, h2)
p (h == h3) # => true

A few notes:

  • This solution depends on the hash structures; it may not work if the structures are changed.
  • Marshal.load(Marshal.dump(h1)) is used to make a "deep copy" of h1, so that h1 is not modified. If h1 can be modified, merge_em(h1, h2) is sufficient, with h1 being the merged hash.
  • The gem awesome_print gives you a nicely-formatted display of complex structures like these. All you'd need to do here is require 'ap' followed by ap h. Try it!


Related Topics



Leave a reply



Submit