Reputation: 364
How can I deep merge two Ruby hash so that the second hash only override the first hash without adding any key to it?
Example:
Merge
{
"dog" => {
"ear" => "big",
"hair" => "smooth"
}
}
with
{
"dog" => {
"ear" => "small",
"tail" => "curve"
}
}
will have result
{
"dog" => {
"ear" => "small", # Only override key "ear"
"hair" => "smooth"
# Without adding key "tail"
}
}
Upvotes: 1
Views: 716
Reputation: 16505
There is the example here to deep merge hash with an other value, even it is not a hash.
class Hash
def deep_merge other
return self if other.nil? or other == {}
other_hash = other.is_a?(Hash) && other || { nil => other }
common_keys = self.keys & other_hash.keys
base_hash = (other_hash.keys - common_keys).reduce({}) do |res, key|
res[key] = other_hash[key]
res
end
self.reduce(base_hash) do |res, (key, value)|
new =
if common_keys.include?(key)
case value
when Hash
value.deep_merge(other_hash[key])
when Array
value.concat([ other_hash[key] ].compact.flatten(1))
when NilClass
other_hash[key]
else
[ value, other_hash[key] ].compact.flatten(1)
end
else
value
end
res[key] = new
res
end
end
end
It allows to correctly merge embedded values, when they are of incompatible types, like hash and array, array and non-array, etc.
Upvotes: 0
Reputation: 1120
I think your answer is easy-understanding enough.
To know if a hash includes some key, you can just use has_key?
, key?
or include?
methods.
Here is my code.
Hash.send :define_method, :exclusive_merge do |other|
other.each do |k, v|
if self.has_key?(k)
if self[k].is_a?(Hash) and v.is_a?(Hash)
self[k].exclusive_merge v
else
self[k] = v
end
end
end
self
end
Upvotes: 0
Reputation: 110725
Recursively (and similar to @Tim's answer, which I hadn't seen when I started),
def merge_em(h1, h2)
h1.each_key do |k|
if h2.key?(k)
if h1[k].is_a? Hash
merge_em(h1[k], h2[k])
else
h1[k] = h2[k]
end
end
end
end
h = Marshal.load(Marshal.dump(h1))
merge_em(h, h2)
I've used Marshal.load(Marshal.dump(h1))
to make a deep copy of h1
. If it's OK to change h1
, just
merge_em(h1, h2)
Upvotes: 0
Reputation: 1184
Seems like something I would ask in an interview. I hope you're not cheating ;-)
def my_merge(h1, h2)
h1.inject({}) do |h, (k, v)|
if Hash === v
h[k] = my_merge(v, h2[k] || {})
else
h[k] = h2[k] || h1[k]
end
h
end
end
And a test :-)
require 'minitest/autorun'
describe 'my_merge' do
it "maintains same keys" do
h1 = {
"dog" => {
"ear" => "big",
"hair" => "smooth"
}
}
h2 = {
"dog" => {
"ear" => "small",
"tail" => "curve"
}
}
expected = {
"dog" => {
"ear" => "small", # Only override key "ear"
"hair" => "smooth"
# Without adding key "tail"
}
}
my_merge(h1, h2).must_equal(expected)
end
end
Upvotes: 1
Reputation: 364
So I answer my own question with two versions:
Procedural style
def exclusive_deep_merge(merge_to, merge_from)
merged = merge_to.clone
merge_from.each do |key, value|
# Only override existing key
if merged.keys.include?(key)
# Deep merge for nested hash
if value.is_a?(Hash) && merged[key].is_a?(Hash)
merged[key] = exclusive_deep_merge(merged[key], value)
else
merged[key] = value
end
end
end
merged
end
The monkey-patching
class Hash
def exclusive_deep_merge(other_hash)
dup.exclusive_deep_merge!(other_hash)
end
def exclusive_deep_merge!(other_hash)
other_hash.each_pair do |k,v|
if self.keys.include? k
self[k] = self[k].is_a?(Hash) && v.is_a?(Hash) ? self[k].exclusive_deep_merge(v) : v
end
end
self
end
end
Any comments on improvement are warmly welcome ♥
Upvotes: 1