Stan Morsky
Stan Morsky

Reputation: 53

Sorting a hash of hashes in ruby

How can I sort this hash of hashes by "clients". I tried using sort_by, but this transforms it into an array of hashes. I am using JSON.parse to create this object from a json file. Thanks!

{
  "default_attributes": {
    "clients": {
      "ABC": {
        "db_name": "databaseabc"
      },
      "HIJ": {
        "db_name": "databasehij"
      },
      "DEF": {
        "db_name": "databasedef"
      }
    }
  }
}

Upvotes: 0

Views: 795

Answers (4)

dtorgo
dtorgo

Reputation: 2116

One of our interns came up with a pretty slick gem to perform deep sorts on hashes/arrays:

def deep_sort_by(&block)
  Hash[self.map do |key, value|
    [if key.respond_to? :deep_sort_by
      key.deep_sort_by(&block)
    else
      key
    end,

    if value.respond_to? :deep_sort_by
      value.deep_sort_by(&block)
    else
      value
    end]

  end.sort_by(&block)]
end

You can inject it into all hashes and then just call it like this:

[myMap.deep_sort_by { |obj| obj }][1]

The code would be similar for an array. We published "deepsort" as a gem for others to use. See "Deeply Sort Nested Ruby Arrays And Hashes" for additional details.

Disclaimer: I work for this company.

Upvotes: 0

Cary Swoveland
Cary Swoveland

Reputation: 110675

hash = {
  default_attributes: {
    clients: {
      ABC: {
        "db_name": "databaseabc"
      },
      HIJ: {
        "db_name": "databasehij"
      },
      DEF: {
        "db_name": "databasedef"
      }
    }
  }
}

If you do not wish to mutate hash, it's easiest to first make a deep copy:

h = Marshal.load(Marshal.dump(hash))

and then sort the relevant part of h:

h[:default_attributes][:clients] =
  h[:default_attributes][:clients].sort.to_h
h
  #=> {:default_attributes=>
  #     {:clients=>
  #       {:ABC=>{:db_name=>"databaseabc"},
  #        :DEF=>{:db_name=>"databasedef"},
  #        :HIJ=>{:db_name=>"databasehij"}}}} 

Confirm hash was not mutated:

hash
  #=> {:default_attributes=>
  #     {:clients=>
  #       {:ABC=>{:db_name=>"databaseabc"},
  #        :HIJ=>{:db_name=>"databasehij"},
  #        :DEF=>{:db_name=>"databasedef"}}}} 

Upvotes: 0

the Tin Man
the Tin Man

Reputation: 160551

Why do you want to sort a hash? There's no advantage to it. Instead, get the keys, sort those, then use the keys to retrieve the data in the order you want.

For instance:

hash = {'z' => 26, 'a' => 1}
sorted_keys = hash.keys.sort # => ["a", "z"]
hash.values_at(*sorted_keys) # => [1, 26]

Using your example hash:

hash = {
  "default_attributes": {
    "clients": {
      "ABC": {
        "db_name": "databaseabc"
        },
        "HIJ": {
          "db_name": "databasehij"
          },
          "DEF": {
            "db_name": "databasedef"
          }
        }
      }
    }
clients = hash[:default_attributes][:clients]
sorted_keys = clients.keys.sort # => [:ABC, :DEF, :HIJ]
clients.values_at(*sorted_keys) 
# => [{:db_name=>"databaseabc"},
#     {:db_name=>"databasedef"},
#     {:db_name=>"databasehij"}]

Or:

sorted_keys.each do |k|
  puts clients[k][:db_name]
end
# >> databaseabc
# >> databasedef
# >> databasehij

Note: From looking at your "hash", it really looks like a JSON string missing the original surrounding { and }. If it is, this question becomes somewhat of an "XY problem". The first question should be "how do I convert a JSON string back to a Ruby object?":

require 'json'

hash = '{
  "default_attributes": {
    "clients": {
      "ABC": {
        "db_name": "databaseabc"
        },
        "HIJ": {
          "db_name": "databasehij"
          },
          "DEF": {
            "db_name": "databasedef"
          }
        }
      }
    }'

foo = JSON[hash]
# => {"default_attributes"=>
#      {"clients"=>
#        {"ABC"=>{"db_name"=>"databaseabc"},
#         "HIJ"=>{"db_name"=>"databasehij"},
#         "DEF"=>{"db_name"=>"databasedef"}}}}

At that point foo would contain a regular hash, and the inconsistent symbol definitions like "default_attributes": and "clients": would make sense because they ARE JSON hash keys, and the resulting parsed object would be a standard Ruby hash definition. And, you'll have to adjust the code above to access the individual nested hash keys.

Upvotes: 2

Chris Heald
Chris Heald

Reputation: 62648

If you are using Ruby <1.9, hashes are order-undefined. Sorting them makes no sense.

Ruby 1.9+ has ordered hashes; you would use sort_by, then convert your array of hashes back into a hash. Ruby 2.0+ provides Array#to_h for this.

 data["default_attributes"]["clients"] = data["default_attributes"]["clients"].sort_by(&:first).to_h

Upvotes: 1

Related Questions