Reputation: 53
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
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
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
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
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