165

Is there an easy or elegant way to merge two hashes without overwriting duplicate keys?

That is, if the key is present in the original hash I don't want to change its value.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Claudio Acciaresi
  • 31,951
  • 5
  • 33
  • 43

5 Answers5

271

If you have two hashes, options and defaults, and you want to merge defaults into options without overwriting existing keys, what you really want to do is the reverse: merge options into defaults:

options = defaults.merge(options)

Or, if you're using Rails you can do:

options.reverse_merge!(defaults)
Alex Reisner
  • 29,124
  • 6
  • 56
  • 53
23

There is a way in standard Ruby library to merge Hashes without overwriting existing values or reassigning the hash.

important_hash.merge!(defaults) { |key, important, default| important }
ujifgc
  • 2,215
  • 2
  • 19
  • 21
3

If your problems is that the original hash and the second one both may have duplicate keys and you don't want to overwrite in either direction, you might have to go for a simple manual merge with some kind of collision check and handling:

hash2.each_key do |key|
  if ( hash1.has_key?(key) )
       hash1[ "hash2-originated-#{key}" ] = hash2[key]
  else
       hash1[key]=hash2[key]
  end
end

Obviously, this is very rudimentary and assumes that hash1 doesn't have any keys called "hash2-originated-whatever" - you may be better off just adding a number to the key so it becomes key1, key2 and so on until you hit on one that isn't already in hash1. Also, I haven't done any ruby for a few months so that's probably not syntactically correct, but you should be able to get the gist.

Alternatively redefine the value of the key as an array so that hash1[key] returns the original value from hash1 and the value from hash2. Depends what you want your outcome to be really.

glenatron
  • 11,018
  • 13
  • 64
  • 112
  • How about if not keeping both key, but adding up the values of the same key? – Tom K. C. Chiu May 28 '15 at 05:19
  • 1
    @TomK.C.Chiu That would very much depend on circumstances that we can't judge from the question - what if the values in hash1 are strings and hash2 are integers? For some cases that might be a viable option, but more often it would cause problems- the suggestion of using lists for values works around this quite cleanly. – glenatron May 28 '15 at 09:03
1

Here you can merge your 2 hash by reverse_merge

order = {
 id: 33987,
 platform: 'web'
}

user = {
  name: 'Jhon Doe',
  email: 'jhon.doe@gmail.com' 
}
newHash = order.reverse_merge!(user)
render json: { data: newHash, status: 200 }

# => {:name=>"Jhon Doe", :email=>"jhon.doe@gmail.com", :id=>33987, :platform=>"web"}
Gonzalo S
  • 896
  • 11
  • 19
0

If you want to merge the two hashes options and defaults without overwriting the destination hash, you may check with select if the key is already present in the destination hash. Here's the pure Ruby solution without Rails:

options  = { "a" => 100, "b" => 200 }
defaults = { "b" => 254, "c" => 300 }
options.merge!(defaults.select{ |k,_| not options.has_key? k })

# output
# => {"a"=>100, "b"=>200, "c"=>300}

Or if the key is present, but contains nil and you want to overwrite it:

options.merge!(defaults.select{ |k,_| options[k].nil? })
Christian
  • 11
  • 4