Why comparing Ruby hashes by eye is so painful
If you write Ruby, you have lived this moment. A test fails, and your terminal prints two hashes that are almost, but not quite, identical:
-{"uid"=>"user@example.test", "active"=>true, "role"=>"admin", "seats"=>3}
+{"uid"=>"user@example.test", "active"=>true, "role"=>"editor", "seats"=>3}
Somewhere in there, one value changed. Your eyes start at the left edge
and crawl across character by character, comparing two strings that were
deliberately built to look the same. With four keys it is annoying. With
forty keys, or hashes nested three levels deep, or keys that come back in
a different order on every run, it becomes genuinely error-prone. You miss
the difference, assume the test is flaky, and lose ten minutes before you
spot that role
quietly went from "admin"
to "editor".
That is exactly the kind of mechanical comparison a computer should be doing for you. RubyHash does it. Paste the two lines and it tells you, instantly and unambiguously, which key changed and what it changed to.
How RubyHash works
The tool runs four steps on whatever you paste, all locally in your browser:
- 1
Parse the Ruby hash syntax
A real parser reads the input into an actual data structure, understanding hashrockets, symbol keys, nesting, and quoting, rather than treating it as a string to be find-and-replaced.
- 2
Convert to JSON and sort the keys
Keys are sorted alphabetically so that ordering differences, which are common when hashes come from database rows or serializers, stop masquerading as real changes.
- 3
Render a side-by-side diff
The two structures are diffed with word-level highlighting, so the single value that actually changed lights up while everything identical stays quiet. Toggle to a unified view for narrow screens.
- 4
Flag type changes
When a field changes type,
nilto a string, an integer to a float, a string to a hash, RubyHash calls it out explicitly. These are the changes most likely to cause a downstream bug and the easiest to miss by eye.
Why a real parser, not a regex
The tempting shortcut is to swap every =>
for a colon, wrap the result in braces, and hand it to a JSON parser. That
works for the simplest hashes and falls apart the moment your data gets
interesting. Real Ruby output is full of cases a naive replace gets wrong:
- → Mixed key styles in one hash:
{:name => "x", "role" => :admin} - → Quoted strings that contain the very characters you are splitting on, like commas, colons, or braces inside a value
- → Deeply nested hashes and arrays, where a single pass of replacements cannot track which brace closes what
- → Ruby literals like
nil,true, andfalsethat must become valid JSON
RubyHash walks the input character by character and understands the grammar of a Ruby hash. That is the difference between a diff you can trust and one that occasionally tears a string in half and lies to you. When the output says nothing else changed, you can believe it.
Who this is for
RubyHash started as a tool for everyday Rails work, where serializer tests, API response assertions, and ActiveRecord attribute comparisons routinely produce hashes too big to eyeball. If any of these sound familiar, the tool will save you time:
- • A Minitest or RSpec assertion failed and dumped two large hashes
- • You are debugging a JSON API and the response does not match what you expected
- • A serializer changed and you need to see exactly which fields moved
- • You are reviewing a pull request that changes a fixture or a config hash
- • You copied a hash out of a Rails console and want it as clean, sorted JSON
Frequently asked questions
What format should I paste in? +
Paste the two lines straight from your test runner, including the leading - and + that Minitest adds to the expected and actual values. RubyHash also accepts a single hash on its own, or two hashes on separate lines without the diff markers.
Does my data get sent anywhere? +
No. All parsing and diffing happens in your browser with JavaScript. The hashes you paste are never uploaded, logged, or stored on a server. You can disconnect from the internet after the page loads and the tool still works.
Does it handle nested hashes and arrays? +
Yes. The parser is a hand-written recursive descent parser, so it understands hashes inside arrays inside hashes to any depth, along with mixed hashrocket and symbol syntax in the same structure.
What about nil, symbols, and other Ruby types? +
nil becomes null, symbols are preserved as readable keys, and true/false map to JSON booleans. When a value changes type between the two hashes, for example nil to a string or an integer to a float, RubyHash flags it explicitly instead of letting it slip by.
Is RubyHash free? +
Yes, completely free, with no sign-up and no installation. It runs entirely in the browser. The site is supported by unobtrusive ads and an optional newsletter.
From the blog
View all posts →Alongside the tool, the blog covers the Ruby and Rails topics that come up while writing and debugging this kind of code: testing, hashes, pattern matching, value objects, and more.
JSON.parse with symbolize_names: Symbol Keys, and Everything Else That Does Not Come Back
JSON.parse hands you string keys unless you pass symbolize_names: true, and even then a round trip through JSON is not lossless. Here is what the option does, what it cannot restore, and how to compare parsed JSON in tests.
transform_keys and transform_values in Ruby: What They Change and What They Quietly Skip
transform_keys and transform_values are the cleanest way to reshape a Ruby hash, but they are shallow, they drop defaults, and they can collapse keys without warning. Here is how they behave and where they bite.
How to Sort a Hash by Key in Ruby (and Why You Get an Array Back)
Calling sort on a Ruby hash hands you an array of pairs, not a hash. Here is why that happens, how to get a sorted hash back, and the cases where sorting actually buys you something.
Comparing Arrays of Hashes in Ruby Without Order Ruining It
Comparing two arrays of hashes with == fails the moment the order changes, even when the data is identical. Here is why it happens and the patterns that actually compare the content.