 mystix
11/22/2012 - 6:36 AM

## Recursively diff two Ruby hashes.

Recursively diff two Ruby hashes.

``````# Recursively diff two hashes, showing only the differing values.
# By Henrik Nyh <http://henrik.nyh.se> 2009-07-14 under the MIT license.
#
# Example:
#
# a = {
#   "same"   => "same",
#   "diff"   => "a",
#   "only a" => "a",
#   "nest" => {
#     "same" => "same",
#     "diff" => "a"
#   }
# }
#
# b = {
#   "same"   => "same",
#   "diff"   => "b",
#   "only b" => "b",
#   "nest" => {
#     "same" => "same",
#     "diff" => "b"
#   }
# }
#
# a.deep_diff(b)  # =>
#
# {
#   "diff"   => ["a", "b"],
#   "only a" => ["a", nil],
#   "only b" => [nil, "b"],
#   "nest" => {
#     "diff" => ["a", "b"]
#   }
# }
#
#
# ActiveSupport's Hash#diff would give this for a.diff(b):
#
# {
#   "diff"   => "a",
#   "only a" => "a",
#   "only b" => "b",
#   "nest" => {
#     "same" => "same",
#     "diff" => "a"
#   }
# }
#

class Hash

def deep_diff(b)
a = self
(a.keys | b.keys).inject({}) do |diff, k|
if a[k] != b[k]
if a[k].respond_to?(:deep_diff) && b[k].respond_to?(:deep_diff)
diff[k] = a[k].deep_diff(b[k])
else
diff[k] = [a[k], b[k]]
end
end
diff
end
end

end

if __FILE__ == \$0
require "test/unit"

class DeepDiffTest < Test::Unit::TestCase
def assert_deep_diff(diff, a, b)
assert_equal(diff, a.deep_diff(b))
end

def test_no_difference
assert_deep_diff(
{},
{"one" => 1, "two" => 2},
{"two" => 2, "one" => 1}
)
end

def test_fully_different
assert_deep_diff(
{"one" => [1, nil], "two" => [nil, 2]},
{"one" => 1},
{"two" => 2}
)
end

def test_simple_difference
assert_deep_diff(
{"one" => [1, "1"]},
{"one" => 1},
{"one" => "1"}
)
end

def test_complex_difference
assert_deep_diff(
{
"diff" => ["a", "b"],
"only a" => ["a", nil],
"only b" => [nil, "b"],
"nested" => {
"y" => {
"diff" => ["a", "b"]
}
}

},

{
"one"    => "1",
"diff"   => "a",
"only a" => "a",
"nested" => {
"x" => "x",
"y" => {
"a"    => "a",
"diff" => "a"
}
}
},

{
"one"    => "1",
"diff"   => "b",
"only b" => "b",
"nested" => {
"x" => "x",
"y" => {
"a" => "a",
"diff" => "b"
}
}
}

)
end

def test_default_value
assert_deep_diff(
{"one" => [1, "default"]},
{"one" => 1},
Hash.new("default")
)
end

end

end
``````