Ruby: merge nested hash
Solution 1
For rails 3.0.0+ or higher version there is the deep_merge function for ActiveSupport that does exactly what you ask for.
Solution 2
I found a more generic deep-merge algorithm here, and used it like so:
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
a.deep_merge(b)
Solution 3
To add on to Jon M and koendc's answers, the below code will handle merges of hashes, and :nil as above, but it will also union any arrays that are present in both hashes (with the same key):
class ::Hash
def deep_merge(second)
merger = proc { |_, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
merge(second.to_h, &merger)
end
end
a.deep_merge(b)
Solution 4
For variety's sake - and this will only work if you want to merge all the keys in your hash in the same way - you could do this:
a.merge(b) { |k, x, y| x + y }
When you pass a block to Hash#merge
, k
is the key being merged, where the key exists in both a
and b
, x
is the value of a[k]
and y
is the value of b[k]
. The result of the block becomes the value in the merged hash for key k
.
I think in your specific case though, nkm's answer is better.
Solution 5
A little late to answer your question, but I wrote a fairly rich deep merge utility awhile back that is now maintained by Daniel Deleo on Github: https://github.com/danielsdeleo/deep_merge
It will merge your arrays exactly as you want. From the first example in the docs:
So if you have two hashes like this:
source = {:x => [1,2,3], :y => 2}
dest = {:x => [4,5,'6'], :y => [7,8,9]}
dest.deep_merge!(source)
Results: {:x => [1,2,3,4,5,'6'], :y => 2}
It won't merge :y (because int and array aren't considered mergeable) - using the bang (!) syntax causes the source to overwrite.. Using the non-bang method will leave dest's internal values alone when an unmergeable entity is found. It will add the arrays contained in :x together because it knows how to merge arrays. It handles arbitrarily deep merging of hashes containing whatever data structures.
Lots more docs on Daniel's github repo now..
user1223862
Updated on December 11, 2020Comments
-
user1223862 over 3 years
I would like to merge a nested hash.
a = {:book=> [{:title=>"Hamlet", :author=>"William Shakespeare" }]} b = {:book=> [{:title=>"Pride and Prejudice", :author=>"Jane Austen" }]}
I would like the merge to be:
{:book=> [{:title=>"Hamlet", :author=>"William Shakespeare"}, {:title=>"Pride and Prejudice", :author=>"Jane Austen"}]}
What is the nest way to accomplish this?