How to Elegantly Symbolize_Keys for a 'Nested' Hash

How to elegantly symbolize_keys for a 'nested' hash

There are a few ways to do this

  1. There's a deep_symbolize_keys method in Rails

    hash.deep_symbolize_keys!

  2. 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.

  3. There is another method called with_indifferent_access. This allows you to access a hash with either a string or a symbol like how params are in the controller. This method doesn't have a bang counterpart.

    hash = hash.with_indifferent_access

  4. 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



Leave a reply



Submit