Ruby: merge nested hash

44,166

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..

Share:
44,166
user1223862
Author by

user1223862

Updated on December 11, 2020

Comments

  • user1223862
    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?