Skip to content

Instantly share code, notes, and snippets.

@agius
Created May 8, 2012 00:52
Show Gist options
  • Save agius/2631752 to your computer and use it in GitHub Desktop.
Save agius/2631752 to your computer and use it in GitHub Desktop.
Ruby JSON Diff
#!/usr/bin/env ruby
require 'rubygems'
require 'json'
unless ARGV.count > 1
puts "Usage: json_diff.rb json_file_1.json json_file_2.json"
exit
end
def different?(a, b, bi_directional=true)
return [a.class.name, nil] if !a.nil? && b.nil?
return [nil, b.class.name] if !b.nil? && a.nil?
differences = {}
a.each do |k, v|
if !v.nil? && b[k].nil?
differences[k] = [v, nil]
next
elsif !b[k].nil? && v.nil?
differences[k] = [nil, b[k]]
next
end
if v.is_a?(Hash)
unless b[k].is_a?(Hash)
differences[k] = "Different types"
next
end
diff = different?(a[k], b[k])
differences[k] = diff if !diff.nil? && diff.count > 0
elsif v.is_a?(Array)
unless b[k].is_a?(Array)
differences[k] = "Different types"
next
end
c = 0
diff = v.map do |n|
if n.is_a?(Hash)
diffs = different?(n, b[k][c])
c += 1
["Differences: ", diffs] unless diffs.nil?
else
c += 1
[n , b[c]] unless b[c] == n
end
end.compact
differences[k] = diff if diff.count > 0
else
differences[k] = [v, b[k]] unless v == b[k]
end
end
return differences if !differences.nil? && differences.count > 0
end
json_a = JSON.parse(File.read(ARGV[0]))
json_b = JSON.parse(File.read(ARGV[1]))
differences = different?(json_a, json_b)
if ARGV[2]
File.open(ARGV[2], 'w'){ |f| f.write(JSON.pretty_generate(differences)) }
else
puts JSON.pretty_generate(differences)
end
@hackervera
Copy link

Hey awesome, i used this! (linked back to your gist in src)

@krogebry
Copy link

krogebry commented Oct 2, 2012

Using this in a script, thanks a ton, very handy.

@squanto
Copy link

squanto commented May 21, 2013

Legit. +1

@shevkoplyas
Copy link

Hey agius, thanks! nice code!
I spotted a bug when comparing quite complex nested structures. Here's simplified test case to reproduce the bug behavior:

irb(main):001:0> h = {"a" => [11,22,33] }
=> {"a"=>[11, 22, 33]}
irb(main):002:0> different?( h, h) # <--- we compare h to itself
=> {"a"=>[[11, nil], [22, nil], [33, nil]]} # <--- we got not nil, which is an error

Here's fixed code:

  diff = v.map do |n|
    if n.is_a?(Hash)
      diffs = different?(n, b[k][c])
      c += 1
      ["Differences: ", diffs] if !diffs.nil? && diffs.count>0
    else
      c += 1
      #[n , b[c]] unless b[c] == n             # OLD BUGGY LINE (missing [k] and going 1 item ahead due to c+=1 above)
      [n , b[c-1]] unless b[k][c-1] == n       # NEW SHINY BUGLESS THING!-)
    end
  end.compact

and now let us replay the test case:

irb(main):001:0> h = {"a" => [11,22,33] }
=> {"a"=>[11, 22, 33]}
irb(main):002:0> different?( h, h)
=> nil

here you go!

thanx again..

@shevkoplyas
Copy link

ops! another bug: since you iterate over 1st object (of two objects to compare), your function won't notice if 2nd object have all the same elements as the 1st one PLUS some extra elements.

look:

irb(main):012:0> h1 = {"a" => [11,22,33] }
=> {"a"=>[11, 22, 33]}
irb(main):013:0> h2 = {"a" => [11,22,33,44] } # <--- h2 is same as h1 PLUS one more extra element
=> {"a"=>[11, 22, 33, 44]}
irb(main):014:0> different?( h1, h2) # <--- we iterate h1 (smaller one)
=> nil # and we spotted NO difference!
irb(main):015:0> different?( h2, h1) # only if we compare them backwards
=> {"a"=>[[44, nil]]} # then we have our diff

so temp workaround would be to wrap your f-n and wrapper will call it twice (h1,h2) and (h2,h1), but then functions results representation if diffs are present would be ugly.. It is ok for my case as I'm only interested if they're exact same or not. (I don't care about diffs:)

thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment