Deep Merge Nested Hashes In Ruby Without Losing Keys
Ruby’s built-in Hash#merge only merges the top level — nested keys get overwritten entirely instead of merged recursively. When working with deeply nested configuration objects or API payloads, you need a merge that walks the full structure.
Description
Hash#merge does a shallow merge: if both hashes have the same key at the top level and its value is itself a hash, the source value replaces the destination value entirely. deep_merge recursively merges nested hashes, preserving keys from both sides and only overwriting at the leaf level where values genuinely conflict.
Rails ships deep_merge as part of ActiveSupport. In plain Ruby, you implement it with a simple recursive approach using merge with a block.
This pattern is essential for merging configuration files, layering environment-specific overrides onto defaults, or combining API response objects.
Sample input:
defaults = {
database: { pool: 5, timeout: 30 },
cache: { ttl: 300 }
}
overrides = {
database: { pool: 10 },
logging: { level: "info" }
}
Sample Output:
{
database: { pool: 10, timeout: 30 },
cache: { ttl: 300 },
logging: { level: "info" }
}
Answer
def deep_merge(base, overrides)
base.merge(overrides) do |_key, old_val, new_val|
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
deep_merge(old_val, new_val)
else
new_val
end
end
end
# In Rails (ActiveSupport):
# defaults.deep_merge(overrides)
Check viewARU - Brand Newsletter!
Newsletter to DEVs by DEVs - boost your Personal Brand & career! 🚀