Recursively Sort Ruby Hash By Key
Here’s a quick and easy way to sort a Ruby hash by its keys. The ability to sort recursively and provide a custom sort block are also available features.
The Method #
I took the approach of monkey patching a sort_by_key method into the Ruby Hash class itself. The method can be easily modified and placed into a utility class if you’d prefer to take more procedural approach.
class Hash
def sort_by_key(recursive = false, &block)
self.keys.sort(&block).reduce({}) do |seed, key|
seed[key] = self[key]
if recursive && seed[key].is_a?(Hash)
seed[key] = seed[key].sort_by_key(true, &block)
end
seed
end
end
end
Basic Usage #
Below is a very disorganized nested hash and an example of using the recursive sort option to get it back into line using Hash.sort_by_key(true).
h = {
"b" => 2, "c" => 3, "a" => 1, "d" => {
"b" => 2, "c" => 3, "a" => 1, "d" => {
"b" => 2, "c" => 3, "a" => 1
}
}
}
h.sort_by_key(true) # =>
{
"a" => 1, "b" => 2, "c" => 3, "d" => {
"a" => 1, "b" => 2, "c" => 3, "d" => {
"a" => 1, "b" => 2, "c" => 3
}
}
}
What About Mixed Type Hash Keys!? #
If we continued the example from above by executing h[:a] = "one", and then calling h.sort_by_key, we would be slapped in the face with an exception of …
ArgumentError: comparison of String with :a failed
Looking at the backtrace it appears the call to self.keys.sort is the culprit.
So What’s The Solution? #
There are a couple of solutions depending on your coding style and opinion on Ruby convention.
Option 1: Don’t do that! #
Many coders in the Ruby community will argue that it’s not the job of Ruby to undo a user’s mistakes. While “Don’t do that!” that can sound like a copout, it’s also really good advice – keep things simple.
Generally, it’s not a good idea to mix the types of Hash keys. If you find that you’re getting this exception it’s probably an indication that “You’re doing it the wrong way.”
Option 2: Passing a custom sort block, and casting key values… #
Coming from PHP I’ve grown accustomed to certain… luxuries, in that if you ask PHP to ksort() an array it will do its very best to get you something reasonable back. By passing a custom sort block to Hash.sort_by_keys we can mimic PHP’s robustness.
Let’s change how Hash.sort_by_keys is called by passing it a custom sort block that casts each key to a string before the comparison.
h.sort_by_key(true) {|x, y| x.to_s <=> y.to_s} # =>
{
:a => "one", "a" => 1, "b" => 2, "c" => 3, "d" => {
"a" => 1, "b" => 2, "c" => 3, "d" => {
"a" => 1, "b" => 2, "c" => 3
}
}
}
It’s a very simplistic technique that should be capable of accommodating almost all of the off-the-beaten-path situations out there in the wild.
Summary #
I ended up needing a variant of this solution to address a situation where http parameter ordering had to be alphabetical in order to create and validate signed requests to OAuth based APIs. So far things are working well and my code is a lot cleaner.
In my search for an existing sorting solution I also stumble upon this StackOverflow.com Question that shed some light on other hash key sorting techniques.
License #
This code is released under the New-BSD License.
If you’ve found an issue, improvement, or bug with this technique please Contact Me, or comment below.