How to elegantly symbolize_keys for a 'nested' hash
There are a few ways to do this
There's a
deep_symbolize_keys
method in Railshash.deep_symbolize_keys!
As mentioned by @chrisgeeq, there is a
deep_transform_keys
method that's available from Rails 4.hash.deep_transform_keys(&:to_sym)
There is also a bang
!
version to replace the existing object.There is another method called
with_indifferent_access
. This allows you to access a hash with either a string or a symbol like howparams
are in the controller. This method doesn't have a bang counterpart.hash = hash.with_indifferent_access
The last one is using
JSON.parse
. I personally don't like this because you're doing 2 transformations - hash to json then json to hash.JSON.parse(JSON[h], symbolize_names: true)
UPDATE:
16/01/19 - add more options and note deprecation of deep_symbolize_keys
19/04/12 - remove deprecated note. only the implementation used in the method is deprecated, not the method itself.
How to convert nested string hash to symbol hash using Ruby?
Just use
JSON.parse(result, symbolize_keys: true)
More info http://apidock.com/ruby/JSON/parse
or on the hash itself
hash = { 'name' => 'Rob', 'age' => '28' }
hash.symbolize_keys
# => {:name=>"Rob", :age=>"28"}
http://apidock.com/rails/Hash/symbolize_keys
These don't seem to do it recursively though.
There's also deep_symbolize_keys!
in Rails
http://api.rubyonrails.org/classes/Hash.html#method-i-deep_symbolize_keys
What is the difference between symbolize_key method and other methods to symbolize hash keys in rails?
The difference between symbolize_keys
and deep_symbolize_keys
is, the second one will work for nested hashes where the former one will not.
For example:
hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
hash.symbolize_keys
# => {:person=>{"name"=>"Rob", "age"=>"28"}}
hash.deep_symbolize_keys
# => {:person=>{:name=>"Rob", :age=>"28"}}
And using !
is a convention to modify self
.
For example:
# without bang(!), hash is not affected
hash.deep_symbolize_keys # => {:person=>{:name=>"Rob", :age=>"28"}}
hash # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
# with bang(!), hash is affected
hash.deep_symbolize_keys! # => {:person=>{:name=>"Rob", :age=>"28"}}
hash # => {:person=>{:name=>"Rob", :age=>"28"}}
Why all values of a nested hash are updated when merging another hash?
Object Id of all of them is same, you must make a copy of you elements
hash.
all of them are referring to same address.
there is sallow copy in ruby with :
Object#dup
: https://ruby-doc.org/core-3.0.3/Object.html#method-i-dup
hash = Hash[years.collect { |item| [item, elements.dup] }]
or
Kernel#clone
: https://ruby-doc.org/core-3.0.3/Kernel.html#method-i-clone
you can use ActiveSupport for deep copy too: https://api.rubyonrails.org/classes/Hash.html#method-i-deep_dup
Just for info -> see Ruby Marshal class, its useful for some cases : https://ruby-doc.org/core-3.0.3/Marshal.html
Accessing values in nested hash
To start, you need to understand what iterating over a hash will give you.
Consider this:
exp = {
fam: {cty: "bk", ins: 3},
spec: {cty: "man", ins: 2},
br: {cty: "qns", ins: 1},
aha: {cty: "man", ins: 0}
}
exp.map { |e, c, value| [e, c, value] }
# => [[:fam, {:cty=>"bk", :ins=>3}, nil], [:spec, {:cty=>"man", :ins=>2}, nil], [:br, {:cty=>"qns", :ins=>1}, nil], [:aha, {:cty=>"man", :ins=>0}, nil]]
This is basically what you're doing as you loop and Ruby passes the block the key/value pairs. You're telling Ruby to give you the current hash key in e
, the current hash value in c
and, since there's nothing else being passed in, the value
parameter becomes nil
.
Instead, you need a block variable for the key, one for the value:
exp.map { |k, v| [k, v] }
# => [[:fam, {:cty=>"bk", :ins=>3}], [:spec, {:cty=>"man", :ins=>2}], [:br, {:cty=>"qns", :ins=>1}], [:aha, {:cty=>"man", :ins=>0}]]
Notice that the nil values are gone.
Rewriting your code taking that into account, plus refactoring it for simplicity:
exp = {
fam: {cty: 'bk', ins: 3},
spec: {cty: 'man', ins: 2},
br: {cty: 'qns', ins: 1},
aha: {cty: 'man', ins: 0}
}
exp.each do |k, v|
if v[:cty] == 'man'
puts k
end
end
# >> spec
# >> aha
Now it's returning the keys you want, so it becomes easy to grab the entire hashes. select
is the appropriate method to use when you're trying to locate specific things:
exp = {
fam: {cty: 'bk', ins: 3},
spec: {cty: 'man', ins: 2},
br: {cty: 'qns', ins: 1},
aha: {cty: 'man', ins: 0}
}
e = exp.select { |k, v| v[:cty] == 'man' }
# => {:spec=>{:cty=>"man", :ins=>2}, :aha=>{:cty=>"man", :ins=>0}}
Older versions of Ruby didn't maintain hash output from the hash iterators so we'd have to coerce back to a hash:
e = exp.select { |k, v| v[:cty] == 'man' }.to_h
# => {:spec=>{:cty=>"man", :ins=>2}, :aha=>{:cty=>"man", :ins=>0}}
How to recursively convert keys of Ruby Hashes that are symbols to String
If you are using ActiveSupport
already or are open to using it, then deep_stringify_keys is what you're looking for.
hash = { person: { name: 'Rob', age: '28' } }
hash.deep_stringify_keys
# => {"person"=>{"name"=>"Rob", "age"=>"28"}}
How do I fetch multiple hash keys with a nested hash?
I presume you want to grab the values out in a tuple? You can make an array that contains whatever collection of values you want.
Try the following for name and city:
[imahash[:id][:name], imahash[:location][:city]]
=> ["Alma", "Freeport"]
Best way to convert strings to symbols in hash
In Ruby >= 2.5 (docs) you can use:
my_hash.transform_keys(&:to_sym)
Using older Ruby version? Here is a one-liner that will copy the hash into a new one with the keys symbolized:
my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
With Rails you can use:
my_hash.symbolize_keys
my_hash.deep_symbolize_keys
Related Topics
Connecting to Oracle Db Using Ruby
Ruby on Rails - Is Params a Method or a Hash
Ruby on Rails 4 - What Authentication Gem to Use
Adding Username to Devise Rails 4
Making a Soap Request by Using Xml in Rails
Rails: Is Passenger Standalone Suitable for Production Deployment
Using Will_Paginate Without :Total_Entries to Improve a Lengthy Query
Rvm: How to Use Gems from a Different Ruby
Dealing with Large CSV Files (20G) in Ruby
How to Detect the End of a Method Chain in Ruby
Ruby on Rails Webpacker Can't Find Images Under Asset_Pack_Path
How to Install a Gem Globally Without Sudo Using Rbenv
Connecting to Web Services Using Rails (Http Requests)
Is There a Function Like String#Scan, But Returning Array of Matchdatas
How to Pass an Argument When Calling a View File
How to Write a Rails Mixin That Spans Across Model, Controller, and View
Is a Global Variable Defined Inside a Sinatra Route Shared Between Requests