A summary of data about the Ruby ecosystem.

https://github.com/liufengyun/hashdiff

Hashdiff is a ruby library to to compute the smallest difference between two hashes
https://github.com/liufengyun/hashdiff

Keywords from Contributors

rubygem activejob activerecord mvc crash-reporting static-code-analysis code-formatter rubocop rspec rack

Last synced: about 1 hour ago
JSON representation

Repository metadata

Hashdiff is a ruby library to to compute the smallest difference between two hashes

README.md

Hashdiff Build Status Gem Version

Hashdiff is a ruby library to compute the smallest difference between two hashes.

It also supports comparing two arrays.

Hashdiff does not monkey-patch any existing class. All features are contained inside the Hashdiff module.

Docs: Documentation

WARNING: Don't use the library for comparing large arrays, say ~10K (see #49).

Why Hashdiff?

Given two Hashes A and B, sometimes you face the question: what's the smallest modification that can be made to change A into B?

An algorithm that responds to this question has to do following:

  • Generate a list of additions, deletions and changes, so that A + ChangeSet = B and B - ChangeSet = A.
  • Compute recursively -- Arrays and Hashes may be nested arbitrarily in A or B.
  • Compute the smallest change -- it should recognize similar child Hashes or child Arrays between A and B.

Hashdiff answers the question above using an opinionated approach:

  • Hash can be represented as a list of (dot-syntax-path, value) pairs. For example, {a:[{c:2}]} can be represented as ["a[0].c", 2].
  • The change set can be represented using the dot-syntax representation. For example, [['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]].
  • It compares Arrays using the LCS(longest common subsequence) algorithm.
  • It recognizes similar Hashes in an Array using a similarity value (0 < similarity <= 1).

Usage

To use the gem, add the following to your Gemfile:

gem 'hashdiff'

Quick Start

Diff

Two simple hashes:

a = {a:3, b:2}
b = {}

diff = Hashdiff.diff(a, b)
diff.should == [['-', 'a', 3], ['-', 'b', 2]]

More complex hashes:

a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}}
b = {a:{y:3}, b:{y:3, z:30}}

diff = Hashdiff.diff(a, b)
diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]

Arrays in hashes:

a = {a:[{x:2, y:3, z:4}, {x:11, y:22, z:33}], b:{x:3, z:45}}
b = {a:[{y:3}, {x:11, z:33}], b:{y:22}}

diff = Hashdiff.best_diff(a, b)
diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]]

Patch

patch example:

a = {'a' => 3}
b = {'a' => {'a1' => 1, 'a2' => 2}}

diff = Hashdiff.diff(a, b)
Hashdiff.patch!(a, diff).should == b

unpatch example:

a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1]
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]

diff = Hashdiff.diff(a, b) # diff two array is OK
Hashdiff.unpatch!(b, diff).should == a

Options

The following options are available: :delimiter, :similarity, :strict, :ignore_keys,
:indifferent, :numeric_tolerance, :strip, :case_insensitive, :array_path,
:use_lcs, and :preserve_key_order

:delimiter

You can specify :delimiter to be something other than the default dot. For example:

a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}}
b = {a:{y:3}, b:{y:3, z:30}}

diff = Hashdiff.diff(a, b, delimiter: '\t')
diff.should == [['-', 'a\tx', 2], ['-', 'a\tz', 4], ['-', 'b\tx', 3], ['~', 'b\tz', 45, 30], ['+', 'b\ty', 3]]

:similarity

In cases where you have similar hash objects in arrays, you can pass a custom value for :similarity instead of the default 0.8. This is interpreted as a ratio of similarity (default is 80% similar, whereas :similarity => 0.5 would look for at least a 50% similarity).

:strict

The :strict option, which defaults to true, specifies whether numeric types are compared on type as well as value. By default, an Integer will never be equal to a Float (e.g. 4 != 4.0). Setting :strict to false makes the comparison looser (e.g. 4 == 4.0).

:ignore_keys

The :ignore_keys option allows you to specify one or more keys to ignore, which defaults to [] (none). Ignored keys are ignored at all levels in both hashes. For example:

a = { a: 4, g: 0, b: { a: 5, c: 6, e: 1 }       }
b = {             b: { a: 7, c: 3, f: 1 }, d: 8 }
diff = Hashdiff.diff(a, b, ignore_keys: %i[a f])
diff.should == [['-', 'g', 0], ['-', 'b.e', 1], ['~', 'b.c', 6, 3], ['+', 'd', 8]]

If you wish instead to ignore keys at a particlar level you should
use a custom comparison method instead. For example to diff only at the 2nd level of both hashes:

a = { a: 4, g: 0, b: { a: 5, c: 6, e: 1 }       }
b = {             b: { a: 7, c: 3, f: 1 }, d: 8 }
diff = Hashdiff.diff(a, b) do |path, _e, _a|
  arr = path.split('.')
  true if %w[a f].include?(arr.last) && arr.size == 2 # note '.' is the default delimiter
end
diff.should == [['-', 'a', 4], ['-', 'g', 0], ['-', 'b.e', 1], ['~', 'b.c', 6, 3], ['+', 'd', 8]]

:indifferent

The :indifferent option, which defaults to false, specifies whether to treat hash keys indifferently. Setting :indifferent to true has the effect of ignoring differences between symbol keys (ie. {a: 1} ~= {'a' => 1})

:numeric_tolerance

The :numeric_tolerance option allows for a small numeric tolerance.

a = {x:5, y:3.75, z:7}
b = {x:6, y:3.76, z:7}

diff = Hashdiff.diff(a, b, numeric_tolerance: 0.1)
diff.should == [["~", "x", 5, 6]]

:strip

The :strip option strips all strings before comparing.

a = {x:5, s:'foo '}
b = {x:6, s:'foo'}

diff = Hashdiff.diff(a, b, numeric_tolerance: 0.1, strip: true)
diff.should == [["~", "x", 5, 6]]

:case_insensitive

The :case_insensitive option makes string comparisons ignore case.

a = {x:5, s:'FooBar'}
b = {x:6, s:'foobar'}

diff = Hashdiff.diff(a, b, numeric_tolerance: 0.1, case_insensitive: true)
diff.should == [["~", "x", 5, 6]]

:array_path

The :array_path option represents the path of the diff in an array rather than
a string. This can be used to show differences in between hash key types and
is useful for patch! when used on hashes without string keys.

a = {x:5}
b = {'x'=>6}

diff = Hashdiff.diff(a, b, array_path: true)
diff.should == [['-', [:x], 5], ['+', ['x'], 6]]

For cases where there are arrays in paths their index will be added to the path.

a = {x:[0,1]}
b = {x:[0,2]}

diff = Hashdiff.diff(a, b, array_path: true)
diff.should == [["-", [:x, 1], 1], ["+", [:x, 1], 2]]

This shouldn't cause problems if you are comparing an array with a hash:

a = {x:{0=>1}}
b = {x:[1]}

diff = Hashdiff.diff(a, b, array_path: true)
diff.should == [["~", [:x], {0=>1}, [1]]]

:use_lcs

The :use_lcs option is used to specify whether a
Longest common subsequence
(LCS) algorithm is used to determine differences in arrays. This defaults to
true but can be changed to false for significantly faster array comparisons
(O(n) complexity rather than O(n2) for LCS).

When :use_lcs is false the results of array comparisons have a tendency to
show changes at indexes rather than additions and subtractions when :use_lcs is
true.

Note, currently the :similarity option has no effect when :use_lcs is false.

a = {x: [0, 1, 2]}
b = {x: [0, 2, 2, 3]}

diff = Hashdiff.diff(a, b, use_lcs: false)
diff.should == [["~", "x[1]", 1, 2], ["+", "x[3]", 3]]

:preserve_key_order

By default, the change set is ordered by operation type: deletions (-) first, then updates (~), and finally additions (+).
Within each operation group, keys are sorted alphabetically:

a = {d: 1, c: 1,       a: 1}
b = {d: 2,       b: 2, a: 2}

diff = Hashdiff.diff(a, b)
diff.should == [["-", "c", 1], ["~", "a", 1, 2], ["~", "d", 1, 2], ["+", "b", 2]]

Setting :preserve_key_order to true processes keys in the order they appear in the first hash.
Keys that only exist in the second hash are appended in their original order:

a = {d: 1, c: 1,       a: 1}
b = {d: 2,       b: 2, a: 2}

diff = Hashdiff.diff(a, b, preserve_key_order: true)
diff.should == [["~", "d", 1, 2], ["-", "c", 1], ["~", "a", 1, 2], ["+", "b", 2]]

Specifying a custom comparison method

It's possible to specify how the values of a key should be compared.

a = {a:'car', b:'boat', c:'plane'}
b = {a:'bus', b:'truck', c:' plan'}

diff = Hashdiff.diff(a, b) do |path, obj1, obj2|
  case path
  when  /a|b|c/
    obj1.length == obj2.length
  end
end

diff.should == [['~', 'b', 'boat', 'truck']]

The yielded params of the comparison block is |path, obj1, obj2|, in which path is the key (or delimited compound key) to the value being compared. When comparing elements in array, the path is with the format array[*]. For example:

a = {a:'car', b:['boat', 'plane'] }
b = {a:'bus', b:['truck', ' plan'] }

diff = Hashdiff.diff(a, b) do |path, obj1, obj2|
  case path
  when 'b[*]'
    obj1.length == obj2.length
  end
end

diff.should == [["~", "a", "car", "bus"], ["~", "b[1]", "plane", " plan"], ["-", "b[0]", "boat"], ["+", "b[0]", "truck"]]

When a comparison block is given, it'll be given priority over other specified options. If the block returns value other than true or false, then the two values will be compared with other specified options.

When used in conjunction with the array_path option, the path passed in as an argument will be an array. When determining the ordering of an array a key of "*" will be used in place of the key[*] field. It is possible, if you have hashes with integer or "*" keys, to have problems distinguishing between arrays and hashes - although this shouldn't be an issue unless your data is very difficult to predict and/or your custom rules are very specific.

Sorting arrays before comparison

An order difference alone between two arrays can create too many diffs to be useful. Consider sorting them prior to diffing.

a = {a:'car', b:['boat', 'plane'] }
b = {a:'car', b:['plane', 'boat'] }

Hashdiff.diff(a, b).should == [["+", "b[0]", "plane"], ["-", "b[2]", "plane"]]

b[:b].sort!

Hashdiff.diff(a, b).should == []

Maintainers

License

Hashdiff is distributed under the MIT-LICENSE.


Owner metadata


GitHub Events

Total
Last Year

Committers metadata

Last synced: 1 day ago

Total Commits: 156
Total Committers: 29
Avg Commits per committer: 5.379
Development Distribution Score (DDS): 0.564

Commits in past year: 6
Committers in past year: 3
Avg Commits per committer in past year: 2.0
Development Distribution Score (DDS) in past year: 0.5

Name Email Commits
liufengyun l****a@g****m 68
Krzysztof Rybka k****a@g****m 17
Stephen G s****n@e****m 13
Kevin Dew k****w@m****m 5
Matt Powell m****e@i****m 5
Yuya.Nishida y****a@j****g 5
Ravi Gadad r****i@r****m 4
dfaust d****t@w****m 4
Jeff Felchner c****t@j****m 4
MatzFan m****n@m****m 3
Joe Francis j****e@l****m 3
Douglas Eichelberger d****g@g****m 2
Koichi ITO k****o@g****m 2
Olle Jonsson o****n@g****m 2
Peter Boling p****g@g****m 2
Robert Kiessling r****r@g****m 2
ronco r****o@c****m 2
yuuji.yaginuma y****a@g****m 2
Akira Matsuda r****e@d****p 1
Andreas Zuber z****r@p****h 1
Eric Cohen e****c@g****m 1
Marek n****m@g****m 1
Michael Grosser m****l@g****t 1
Stanisław Pitucha v****r@g****m 1
Thibaut Barrère t****e@g****m 1
Ivan Nixon p****9@g****m 1
Vladimir Kochnev h****e@y****u 1
cloakedcode a****n@a****t 1
moe m****e@b****t 1

Committer domains:


Issue and Pull Request metadata

Last synced: 16 days ago

Total issues: 40
Total pull requests: 68
Average time to close issues: 4 months
Average time to close pull requests: 3 days
Total issue authors: 35
Total pull request authors: 38
Average comments per issue: 3.93
Average comments per pull request: 1.82
Merged pull request: 53
Bot issues: 0
Bot pull requests: 0

Past year issues: 3
Past year pull requests: 4
Past year average time to close issues: about 22 hours
Past year average time to close pull requests: about 10 hours
Past year issue authors: 3
Past year pull request authors: 2
Past year average comments per issue: 0.67
Past year average comments per pull request: 0.0
Past year merged pull request: 2
Past year bot issues: 0
Past year bot pull requests: 0

More stats: https://issues.ecosyste.ms/repositories/lookup?url=https://github.com/liufengyun/hashdiff

Top Issue Authors

  • grosser (4)
  • wimrijnders (2)
  • vsethi (2)
  • ronco (1)
  • renaudpr (1)
  • rmehner (1)
  • jembezmamy (1)
  • jarosite (1)
  • Githraine (1)
  • zenati (1)
  • donaldali (1)
  • rellampec (1)
  • robkiessling (1)
  • jfelchner (1)
  • taufek (1)

Top Pull Request Authors

  • krzysiek1507 (10)
  • nishidayuya (4)
  • stephengroat (4)
  • olleolleolle (4)
  • kevindew (3)
  • jfelchner (3)
  • MatzFan (3)
  • koic (3)
  • ravigadad (2)
  • dduugg (2)
  • liufengyun (2)
  • robkiessling (2)
  • dmitrytrager (1)
  • keram (1)
  • kgooble (1)

Top Issue Labels

Top Pull Request Labels


Package metadata

gem.coop: hashdiff

Hashdiff is a diff lib to compute the smallest difference between two hashes.

  • Homepage: https://github.com/liufengyun/hashdiff
  • Documentation: http://www.rubydoc.info/gems/hashdiff/
  • Licenses: MIT
  • Latest release: 1.2.1 (published 8 months ago)
  • Last Synced: 2026-04-29T18:00:34.069Z (1 day ago)
  • Versions: 29
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 393,071,514 Total
  • Docker Downloads: 2,141,149,653
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 0.029%
    • Docker downloads count: 0.052%
    • Downloads: 0.062%
  • Maintainers (1)
ubuntu-20.04: ruby-hashdiff

  • Homepage: https://github.com/liufengyun/hashdiff
  • Licenses: mit
  • Latest release: 1.0.0-1 (published 3 months ago)
  • Last Synced: 2026-03-13T20:24:47.721Z (about 2 months ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 0.469%
    • Stargazers count: 0.79%
    • Forks count: 1.086%
rubygems.org: hashdiff

Hashdiff is a diff lib to compute the smallest difference between two hashes.

  • Homepage: https://github.com/liufengyun/hashdiff
  • Documentation: http://www.rubydoc.info/gems/hashdiff/
  • Licenses: MIT
  • Latest release: 1.2.1 (published 8 months ago)
  • Last Synced: 2026-04-29T07:28:02.583Z (1 day ago)
  • Versions: 29
  • Dependent Packages: 108
  • Dependent Repositories: 30,642
  • Downloads: 392,916,856 Total
  • Docker Downloads: 2,141,149,653
  • Rankings:
    • Downloads: 0.071%
    • Docker downloads count: 0.127%
    • Dependent repos count: 0.198%
    • Dependent packages count: 0.309%
    • Average: 1.092%
    • Stargazers count: 2.486%
    • Forks count: 3.361%
  • Maintainers (1)
proxy.golang.org: github.com/liufengyun/hashdiff

  • Homepage:
  • Documentation: https://pkg.go.dev/github.com/liufengyun/hashdiff#section-documentation
  • Licenses: mit
  • Latest release: v1.2.1 (published 8 months ago)
  • Last Synced: 2026-04-29T07:28:22.849Z (1 day ago)
  • Versions: 12
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Stargazers count: 2.648%
    • Forks count: 3.152%
    • Average: 6.544%
    • Dependent packages count: 9.576%
    • Dependent repos count: 10.802%
ubuntu-24.04: ruby-hashdiff

  • Homepage: https://github.com/liufengyun/hashdiff
  • Licenses:
  • Latest release: 1.1.0-1 (published 3 months ago)
  • Last Synced: 2026-03-06T15:59:43.825Z (about 2 months ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
debian-10: ruby-hashdiff

  • Homepage: https://github.com/liufengyun/hashdiff
  • Documentation: https://packages.debian.org/buster/ruby-hashdiff
  • Licenses:
  • Latest release: 0.2.3-1 (published 3 months ago)
  • Last Synced: 2026-03-13T19:03:57.445Z (about 2 months ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
ubuntu-23.10: ruby-hashdiff

  • Homepage: https://github.com/liufengyun/hashdiff
  • Licenses:
  • Latest release: 1.0.1-1 (published 3 months ago)
  • Last Synced: 2026-03-14T03:14:27.904Z (about 2 months ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
debian-11: ruby-hashdiff

  • Homepage: https://github.com/liufengyun/hashdiff
  • Documentation: https://packages.debian.org/bullseye/ruby-hashdiff
  • Licenses:
  • Latest release: 1.0.1-1 (published 3 months ago)
  • Last Synced: 2026-03-14T06:23:11.130Z (about 2 months ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
ubuntu-23.04: ruby-hashdiff

  • Homepage: https://github.com/liufengyun/hashdiff
  • Licenses:
  • Latest release: 1.0.1-1 (published 3 months ago)
  • Last Synced: 2026-03-11T15:28:33.229Z (about 2 months ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
guix: ruby-hashdiff

HashDiff computes the smallest difference between two hashes

  • Homepage: https://github.com/liufengyun/hashdiff
  • Documentation: https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/packages/ruby-xyz.scm#n3646
  • Licenses: expat
  • Latest release: 1.0.1 (published about 2 months ago)
  • Last Synced: 2026-04-27T16:20:08.821Z (3 days ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
ubuntu-24.10: ruby-hashdiff

  • Homepage: https://github.com/liufengyun/hashdiff
  • Licenses:
  • Latest release: 1.1.0-1 (published 3 months ago)
  • Last Synced: 2026-03-09T17:06:39.406Z (about 2 months ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
ubuntu-22.04: ruby-hashdiff

  • Homepage: https://github.com/liufengyun/hashdiff
  • Licenses:
  • Latest release: 1.0.1-1 (published 3 months ago)
  • Last Synced: 2026-03-13T22:37:40.670Z (about 2 months ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
debian-12: ruby-hashdiff

  • Homepage: https://github.com/liufengyun/hashdiff
  • Documentation: https://packages.debian.org/bookworm/ruby-hashdiff
  • Licenses:
  • Latest release: 1.0.1-1 (published 3 months ago)
  • Last Synced: 2026-03-13T23:44:33.132Z (about 2 months ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
debian-13: ruby-hashdiff

  • Homepage: https://github.com/liufengyun/hashdiff
  • Documentation: https://packages.debian.org/trixie/ruby-hashdiff
  • Licenses:
  • Latest release: 1.1.0-1 (published 3 months ago)
  • Last Synced: 2026-03-14T18:09:38.879Z (about 2 months ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%

Dependencies

Gemfile rubygems
  • rake >= 0 development
hashdiff.gemspec rubygems
  • bluecloth >= 0 development
  • rspec ~> 3.5 development
  • rubocop >= 1.52.1 development
  • rubocop-rspec > 1.16.0 development
  • yard >= 0 development
.github/workflows/ci.yml actions
  • actions/checkout v4 composite
  • ruby/setup-ruby v1 composite

Score: 32.178159449907504