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 >