Extract value nested with an array of hashes in Ruby - ruby

I have an XML response from a URL that is being converted into an array of hashes which looks like:
{
"EmployeeList"=>{
"EmployeeProfile"=>{
"BuildLoc"=>{"$"=>"1 Happy Place"},
"Status"=>{"$"=>"A"},
"SecrTitle"=>[{}, {}],
"ID"=>{},
"bct"=>{},
"NUM"=>{"$"=>"1234567"},
"BuildCity"=>{"$"=>"Dayton"},
"BuildFloor"=>{"$"=>"6"},
"Expense"=>{"$"=>"1345"},
"LastName"=>{"$"=>"Smith"},
"Middle"=>{},
"SecrName"=>[{}, {}],
"InternalSMTPAddress"=>{"$"=>"Joe.Smith#happy.com"},
"IAddress"=>{"$"=>"Joe.Smith#happy.com"},
"PreferredLastName"=>{},
"DisplayName"=>{"$"=>"Joe Smith"},
"CellPhoneNo"=>{},
"Title"=>{"$"=>"Dr."},
"BuildStreetAddress"=>{"$"=>"123 Happy town"},
"BuildState"=>{"$"=>"IL"},
"FirstName"=>{"$"=>"Joe"},
"AltContactTitle1"=>{},
"Dept-CostCtrNo"=>{"$"=>"129923"},
"PreferredFirstName"=>{"$"=>"Joe"},
"AltContactName2"=>{},
"AltContactPhone2"=>{},
"GDP"=>{},
"BuildZip"=>{"$"=>"112345"},
"RegionID"=>{"$"=>"NAMR"},
"EmploymentType"=>{"$"=>"E"},
"TempPhone"=>{},
"BuildID"=>{"$"=>"01114"},
"CountryAbbr"=>{"$"=>"USA"},
"FaxDisp1"=>{},
"BuildCountry"=>{"$"=>"United States"}
}
},
nil=>nil
}
What's the easiest way to extract the value of "DisplayName" and "InternalSMTPAddress"?

If you assign the returned hash to a variable named "hash" you can access the two desired values for those keys like:
hash['EmployeeList']['EmployeeProfile']['DisplayName']
=> {"$"=>"Joe Smith"}
and
hash['EmployeeList']['EmployeeProfile']['InternalSMTPAddress']
=> {"$"=>"Joe.Smith#happy.com"}
If you want the actual data in them add a trailing ['$']:
hash['EmployeeList']['EmployeeProfile']['DisplayName']['$']
=> "Joe Smith"
hash['EmployeeList']['EmployeeProfile']['InternalSMTPAddress']['$']
=> "Joe.Smith#happy.com"

If you need to find some key in nested hashes use this method:
def find_key(hash,key)
hash.each {|k, v|
return v if k==key
tmp=find_key(v,key) if v.is_a?(Hash)
return tmp unless tmp.nil?
}
return nil
end
Usage:
hash = Hash.new
hash["key1"] = "value1"
hash["key2"] = "value2"
hash["key3"] = Hash.new
hash["key3"]["key4"] = "value4"
hash["key3"]["key5"] = "value5"
hash["key6"] = Hash.new
hash["key6"]["key7"] = "value7"
hash["key6"]["key8"] = Hash.new
hash["key6"]["key8"]["key9"] = "value9"
find_key(hash,"key9") => "value9"
find_key(hash,"key8") => {"key9"=>"value9"}
find_key(hash,"dsfsdfsd") => nil

I would recommend you to use ruby gem nested_lookup.
Install the gem using command gem install nested_lookup
In your case you need the value of 'DisplayName' and 'InternalSMTPAddress'. This is what you need to do.
Rameshs-MacBook-Pro:~ rameshrv$ irb
irb(main):001:0> require 'nested_lookup'
=> true
irb(main):051:0> include NestedLookup
=> Object
# Here the test_data is the data you gave in the question
irb(main):052:0> test_data.nested_get('DisplayName')
=> {"$"=>"Joe Smith"}
irb(main):053:0> test_data.nested_get('InternalSMTPAddress')
=> {"$"=>"Joe.Smith#happy.com"}
irb(main):054:0>
For more information please read https://github.com/rameshrvr/nested_lookup

Related

Remove nil values from hash

I am looking to remove keys from hash that have nil value. article is a class storing each article, and attributes method stores the article as hash.
Expected Result:
{"articles":[{"results":[{"author":null,"title":"Former bar manager jailed for preying on homeless 14-year-old girl","summary":"<p><img src=\"http://images.theage.com.au/2015/08/24/6790912/Thumbnail999662740gisd08image.related.thumbnail.320x214.gj68pg.png1440386418031.jpg-90x60.jpg\" width=\"90\" height=\"60\" style=\"float:left;margin:4px;border:0px\"/></p>A man who preyed on a 14-year-old girl he came across living on the streets of Wodonga has been jailed for nine months.","images":null,"source":null,"date":"Mon, 24 Aug 2015 03:20:21 +0000","guid":"<guid isPermaLink=\"false\">gj68pg</guid>","link":"http://www.theage.com.au/victoria/former-bar-manager-jailed-for-preying-on-homeless-14yearold-girl-20150824-gj68pg.html","section":null,"item_type":null,"updated_date":null,"created_date":null,"material_type_facet":null,"abstract":null,"byline":null,"kicker":null}]}]}
Looking to remove null values from the above output.
def attributes
hash = {
"author" => #author,
"title" => #title,
"summary" => #summary,
"images" => #images,
"source" => #source,
"date" => #date
}
hash = {}
count = 0
article.attributes.each do |key,value|
if value == nil
hash[count] = article.attributes.delete(key)
count += 1
end
end
hash.to_json
The result is as below:
{"0":null,"1":null,"2":null,"3":null,"4":null,"5":null,"6":null,"7":null,"8":null,"9":null,"10":null}
If you're on Ruby >= 2.4.6, you can use compact.
https://apidock.com/ruby/v2_4_6/Hash/compact
Example:
hash = {:foo => nil, :bar => "bar"}
=> {:foo=>nil, :bar=>"bar"}
hash.compact
=> {:bar=>"bar"}
hash
=> {:foo=>nil, :bar=>"bar"}
There's also compact! which removes nil values, or nil if no values in the hash were nil (from version >= 2.5.5).
https://apidock.com/ruby/v2_4_6/Hash/compact%21
Example:
hash
=> {:foo=>nil, :bar=>"bar"}
hash.compact!
=> {:bar=>"bar"}
hash
=> {:bar=>"bar"}
hash.compact!
=> nil
How about trying:
hash = article.attributes.select {|k, v| v }
If the value is false or nil, the attribute will be ignored.
If you want to keep the false value and only eliminate nil, you could run:
hash = article.attributes.select {|k, v| !v.nil? }
You can remove all the nil values from a hash namely "h":
h.delete_if { |k, v| v.nil? }
and you can use this one to remove empty values, too:
h.delete_if { |k, v| v.nil? || v.empty? }
In my case I use refinements to Hash class
module HashUtil
refine Hash do
# simple but can not adapt to nested hash
def squeeze
select{|_, v| !v.nil? }
end
# complex but can adapt to nested hash
def squeeze_deep
each_with_object({}) do |(k, v), squeezed_hash|
if v.is_a?(Hash)
squeezed_hash[k] = v.squeeze
else
squeezed_hash[k] = v unless v.nil?
end
end
end
end
end
class Article
using HashUtil
def attributes
hash = {
"author" => #author,
"title" => #title,
"summary" => #summary,
"images" => #images,
"source" => #source,
"date" => #date
}
hash.squeeze
end
end
The given input is valid JSON, and the question has the JSON tag, so I'd like to offer a generic solution to the problem of recursively eliminating keys in JSON objects, wherever they occur, and no matter what the input JSON may be.
The solution is written in a new JSON-oriented programming language, jq, but even if you cannot use jq, the solution is so brief and elegant that it may suggest an implementation to the general problem in your language of choice.
Here it is - a one-liner:
walk( if type == "object" then with_entries( select(.value != null) ) else . end)
This presupposes jq version 1.5 (see https://stedolan.github.io/jq/).
If you have an older version of jq, the definition of walk/1 can easily be added. (See e.g. Transforming the name of key deeper in the JSON structure with jq)

How do I convert a Ruby hash so that all of its keys are symbols?

I have a Ruby hash which looks like:
{ "id" => "123", "name" => "test" }
I would like to convert it to:
{ :id => "123", :name => "test" }
hash = {"apple" => "banana", "coconut" => "domino"}
Hash[hash.map{ |k, v| [k.to_sym, v] }]
#=> {:apple=>"banana", :coconut=>"domino"}
#mu is too short: Didn't see word "recursive", but if you insist (along with protection against non-existent to_sym, just want to remind that in Ruby 1.8 1.to_sym == nil, so playing with some key types can be misleading):
hash = {"a" => {"b" => "c"}, "d" => "e", Object.new => "g"}
s2s =
lambda do |h|
Hash === h ?
Hash[
h.map do |k, v|
[k.respond_to?(:to_sym) ? k.to_sym : k, s2s[v]]
end
] : h
end
s2s[hash] #=> {:d=>"e", #<Object:0x100396ee8>=>"g", :a=>{:b=>"c"}}
If you happen to be in Rails then you'll have symbolize_keys:
Return a new hash with all keys converted to symbols, as long as they respond to to_sym.
and symbolize_keys! which does the same but operates in-place. So, if you're in Rails, you could:
hash.symbolize_keys!
If you want to recursively symbolize inner hashes then I think you'd have to do it yourself but with something like this:
def symbolize_keys_deep!(h)
h.keys.each do |k|
ks = k.to_sym
h[ks] = h.delete k
symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
end
end
You might want to play with the kind_of? Hash to match your specific circumstances; using respond_to? :keys might make more sense. And if you want to allow for keys that don't understand to_sym, then:
def symbolize_keys_deep!(h)
h.keys.each do |k|
ks = k.respond_to?(:to_sym) ? k.to_sym : k
h[ks] = h.delete k # Preserve order even when k == ks
symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
end
end
Note that h[ks] = h.delete k doesn't change the content of the Hash when k == ks but it will preserve the order when you're using Ruby 1.9+. You could also use the [(key.to_sym rescue key) || key] approach that Rails uses in their symbolize_keys! but I think that's an abuse of the exception handling system.
The second symbolize_keys_deep! turns this:
{ 'a' => 'b', 'c' => { 'd' => { 'e' => 'f' }, 'g' => 'h' }, ['i'] => 'j' }
into this:
{ :a => 'b', :c => { :d => { :e => 'f' }, :g => 'h' }, ['i'] => 'j' }
You could monkey patch either version of symbolize_keys_deep! into Hash if you really wanted to but I generally stay away from monkey patching unless I have very good reasons to do it.
If you are using Rails >= 4 you can use:
hash.deep_symbolize_keys
hash.deep_symbolize_keys!
or
hash.deep_stringify_keys
hash.deep_stringify_keys!
see http://apidock.com/rails/v4.2.1/Hash/deep_symbolize_keys
Just in case you are parsing JSON, from the JSON docs you can add the option to symbolize the keys upon parsing:
hash = JSON.parse(json_data, symbolize_names: true)
Victor Moroz provided a lovely answer for the simple recursive case, but it won't process hashes that are nested within nested arrays:
hash = { "a" => [{ "b" => "c" }] }
s2s[hash] #=> {:a=>[{"b"=>"c"}]}
If you need to support hashes within arrays within hashes, you'll want something more like this:
def recursive_symbolize_keys(h)
case h
when Hash
Hash[
h.map do |k, v|
[ k.respond_to?(:to_sym) ? k.to_sym : k, recursive_symbolize_keys(v) ]
end
]
when Enumerable
h.map { |v| recursive_symbolize_keys(v) }
else
h
end
end
Try this:
hash = {"apple" => "banana", "coconut" => "domino"}
# => {"apple"=>"banana", "coconut"=>"domino"}
hash.tap do |h|
h.keys.each { |k| h[k.to_sym] = h.delete(k) }
end
# => {:apple=>"banana", :coconut=>"domino"}
This iterates over the keys, and for each one, it deletes the stringified key and assigns its value to the symbolized key.
If you're using Rails (or just Active Support):
{ "id" => "123", "name" => "test" }.symbolize_keys
Starting with Ruby 2.5 you can use the transform_key method.
So in your case it would be:
h = { "id" => "123", "name" => "test" }
h.transform_keys!(&:to_sym) #=> {:id=>"123", :name=>"test"}
Note: the same methods are also available on Ruby on Rails.
Here's a Ruby one-liner that is faster than the chosen answer:
hash = {"apple" => "banana", "coconut" => "domino"}
#=> {"apple"=>"banana", "coconut"=>"domino"}
hash.inject({}){|h,(k,v)| h[k.intern] = v; h}
#=> {:apple=>"banana", :coconut=>"domino"}
Benchmark results:
n = 100000
Benchmark.bm do |bm|
bm.report { n.times { hash.inject({}){|h,(k,v)| h[k.intern] = v; h} } }
bm.report { n.times { Hash[hash.map{ |k, v| [k.to_sym, v] }] } }
end
# => user system total real
# => 0.100000 0.000000 0.100000 ( 0.107940)
# => 0.120000 0.010000 0.130000 ( 0.137966)
I'm partial to:
irb
ruby-1.9.2-p290 :001 > hash = {"apple" => "banana", "coconut" => "domino"}
{
"apple" => "banana",
"coconut" => "domino"
}
ruby-1.9.2-p290 :002 > hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
{
:apple => "banana",
:coconut => "domino"
}
This works because we're iterating over the hash and building a new one on the fly. It isn't recursive, but you could figure that out from looking at some of the other answers.
hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
You can also extend core Hash ruby class placing a /lib/hash.rb file :
class Hash
def symbolize_keys_deep!
new_hash = {}
keys.each do |k|
ks = k.respond_to?(:to_sym) ? k.to_sym : k
if values_at(k).first.kind_of? Hash or values_at(k).first.kind_of? Array
new_hash[ks] = values_at(k).first.send(:symbolize_keys_deep!)
else
new_hash[ks] = values_at(k).first
end
end
new_hash
end
end
If you want to make sure keys of any hash wrapped into arrays inside your parent hash are symbolized, you need to extend also array class creating a "array.rb" file with that code :
class Array
def symbolize_keys_deep!
new_ar = []
self.each do |value|
new_value = value
if value.is_a? Hash or value.is_a? Array
new_value = value.symbolize_keys_deep!
end
new_ar << new_value
end
new_ar
end
end
This allows to call "symbolize_keys_deep!" on any hash variable like this :
myhash.symbolize_keys_deep!
def symbolize_keys(hash)
new={}
hash.map do |key,value|
if value.is_a?(Hash)
value = symbolize_keys(value)
end
new[key.to_sym]=value
end
return new
end
puts symbolize_keys("c"=>{"a"=>2,"k"=>{"e"=>9}})
#{:c=>{:a=>2, :k=>{:e=>9}}}
Here's my two cents,
my version of symbolize_keys_deep! uses the original symbolize_keys! provided by rails and just makes a simple recursive call to Symbolize sub hashes.
def symbolize_keys_deep!(h)
h.symbolize_keys!
h.each do |k, v|
symbolize_keys_deep!(v) if v.is_a? Hash
end
end
Facets' Hash#rekey is also a worth mentioning.
Sample:
require 'facets/hash/rekey'
{ "id" => "123", "name" => "test" }.deep_rekey
=> {:id=>"123", :name=>"test"}
There is also a recursive version:
require 'facets/hash/deep_rekey'
{ "id" => "123", "name" => {"first" => "John", "last" => "Doe" } }.deep_rekey
=> {:id=>"123", :name=>{:first=>"John", :last=>"Doe"}}
Here's a little recursive function to do a deep symbolization of the keys:
def symbolize_keys(hash)
Hash[hash.map{|k,v| v.is_a?(Hash) ? [k.to_sym, symbolize_keys(v)] : [k.to_sym, v] }]
end

hash assignment when (key => value) are stored in an array? (ruby)

I have hash (#post) of hashes where I want to keep the order of the hash's keys in the array (#post_csv_order) and also want to keep the relationship key => value in the array.
I don't know the final number of both #post hashes and key => value elements in the array.
I don't know how to assign the hash in a loop for all elements in the array. One by one #post_csv_order[0][0] => #post_csv_order[0][1] works nicely.
# require 'rubygems'
require 'pp'
#post = {}
forum_id = 123 #only sample values.... to make this sample script work
post_title = "Test post"
#post_csv_order = [
["ForumID" , forum_id],
["Post title", post_title]
]
if #post[forum_id] == nil
#post[forum_id] = {
#post_csv_order[0][0] => #post_csv_order[0][1],
#post_csv_order[1][0] => #post_csv_order[1][1]
##post_csv_order.map {|element| element[0] => element[1]}
##post_csv_order.each_index {|index| #post_csv_order[index][0] => #post_csv_order[index][1] }
}
end
pp #post
desired hash assignment should be like that
{123=>{"Post title"=>"Test post", "ForumID"=>123}}
The best way is to use to_h:
[ [:foo,1],[:bar,2],[:baz,3] ].to_h #=> {:foo => 1, :bar => 2, :baz => 3}
Note: This was introduced in Ruby 2.1.0. For older Ruby, you can use my backports gem and require 'backports/2.1.0/array/to_h', or else use Hash[]:
array = [[:foo,1],[:bar,2],[:baz,3]]
# then
Hash[ array ] #= > {:foo => 1, :bar => 2, :baz => 3}
This is available in Ruby 1.8.7 and later. If you are still using Ruby 1.8.6 you could require "backports/1.8.7/hash/constructor", but you might as well use the to_h backport.
I am not sure I fully understand your question but I guess you want to convert a 2d array in a hash.
So suppose you have an array such as:
array = [[:foo,1],[:bar,2],[:baz,3]]
You can build an hash with:
hash = array.inject({}) {|h,e| h[e[0]] = e[1]; h}
# => {:foo=>1, :bar=>2, :baz=>3}
And you can retrieve the keys in correct order with:
keys = array.inject([]) {|a,e| a << e[0] }
=> [:foo, :bar, :baz]
Is it what you were looking for ?
Answers summary
working code #1
#post[forum_id] = #post_csv_order.inject({}) {|h,e| h[e[0]] = e[1]; h}
working code #2
#post[forum_id] = Hash[*#post_csv_order.flatten]
working code #3
#post[forum_id] ||= Hash[ #post_csv_order ] #requires 'require "backports"'

How to change Hash values?

I'd like to replace each value in a hash with value.some_method.
For example, for given a simple hash:
{"a" => "b", "c" => "d"}`
every value should be .upcased, so it looks like:
{"a" => "B", "c" => "D"}
I tried #collect and #map but always just get arrays back. Is there an elegant way to do this?
UPDATE
Damn, I forgot: The hash is in an instance variable which should not be changed. I need a new hash with the changed values, but would prefer not to define that variable explicitly and then loop over the hash filling it. Something like:
new_hash = hash.magic{ ... }
my_hash.each { |k, v| my_hash[k] = v.upcase }
or, if you'd prefer to do it non-destructively, and return a new hash instead of modifying my_hash:
a_new_hash = my_hash.inject({}) { |h, (k, v)| h[k] = v.upcase; h }
This last version has the added benefit that you could transform the keys too.
Since ruby 2.4.0 you can use native Hash#transform_values method:
hash = {"a" => "b", "c" => "d"}
new_hash = hash.transform_values(&:upcase)
# => {"a" => "B", "c" => "D"}
There is also destructive Hash#transform_values! version.
You can collect the values, and convert it from Array to Hash again.
Like this:
config = Hash[ config.collect {|k,v| [k, v.upcase] } ]
This will do it:
my_hash.each_with_object({}) { |(key, value), hash| hash[key] = value.upcase }
As opposed to inject the advantage is that you are in no need to return the hash again inside the block.
There's a method for that in ActiveSupport v4.2.0. It's called transform_values and basically just executes a block for each key-value-pair.
Since they're doing it with a each I think there's no better way than to loop through.
hash = {sample: 'gach'}
result = {}
hash.each do |key, value|
result[key] = do_stuff(value)
end
Update:
Since Ruby 2.4.0 you can natively use #transform_values and #transform_values!.
Try this function:
h = {"a" => "b", "c" => "d"}
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}.
You may want to go a step further and do this on a nested hash. Certainly this happens a fair amount with Rails projects.
Here's some code to ensure a params hash is in UTF-8:
def convert_hash hash
hash.inject({}) do |h,(k,v)|
if v.kind_of? String
h[k] = to_utf8(v)
else
h[k] = convert_hash(v)
end
h
end
end
# Iconv UTF-8 helper
# Converts strings into valid UTF-8
#
# #param [String] untrusted_string the string to convert to UTF-8
# #return [String] your string in UTF-8
def to_utf8 untrusted_string=""
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
ic.iconv(untrusted_string + ' ')[0..-2]
end
I do something like this:
new_hash = Hash[*original_hash.collect{|key,value| [key,value + 1]}.flatten]
This provides you with the facilities to transform the key or value via any expression also (and it's non-destructive, of course).
Ruby has the tap method (1.8.7, 1.9.3 and 2.1.0) that's very useful for stuff like this.
original_hash = { :a => 'a', :b => 'b' }
original_hash.clone.tap{ |h| h.each{ |k,v| h[k] = v.upcase } }
# => {:a=>"A", :b=>"B"}
original_hash # => {:a=>"a", :b=>"b"}
Rails-specific
In case someone only needs to call to_s method to each of the values and is not using Rails 4.2 ( which includes transform_values method link), you can do the following:
original_hash = { :a => 'a', :b => BigDecimal('23.4') }
#=> {:a=>"a", :b=>#<BigDecimal:5c03a00,'0.234E2',18(18)>}
JSON(original_hash.to_json)
#=> {"a"=>"a", "b"=>"23.4"}
Note: The use of 'json' library is required.
Note 2: This will turn keys into strings as well
If you know that the values are strings, you can call the replace method on them while in the cycle: this way you will change the value.
Altohugh this is limited to the case in which the values are strings and hence doesn't answer the question fully, I thought it can be useful to someone.
new_hash = old_hash.merge(old_hash) do |_key, value, _value|
value.upcase
end
# old_hash = {"a" => "b", "c" => "d"}
# new_hash = {"a" => "B", "c" => "D"}

Best way to convert strings to symbols in hash

What's the (fastest/cleanest/straightforward) way to convert all keys in a hash from strings to symbols in Ruby?
This would be handy when parsing YAML.
my_hash = YAML.load_file('yml')
I'd like to be able to use:
my_hash[:key]
Rather than:
my_hash['key']
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
Here's a better method, if you're using Rails:
params.symbolize_keys
The end.
If you're not, just rip off their code (it's also in the link):
myhash.keys.each do |key|
myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
For the specific case of YAML in Ruby, if the keys begin with ':', they will be automatically interned as symbols.
require 'yaml'
require 'pp'
yaml_str = "
connections:
- host: host1.example.com
port: 10000
- host: host2.example.com
port: 20000
"
yaml_sym = "
:connections:
- :host: host1.example.com
:port: 10000
- :host: host2.example.com
:port: 20000
"
pp yaml_str = YAML.load(yaml_str)
puts yaml_str.keys.first.class
pp yaml_sym = YAML.load(yaml_sym)
puts yaml_sym.keys.first.class
Output:
# /opt/ruby-1.8.6-p287/bin/ruby ~/test.rb
{"connections"=>
[{"port"=>10000, "host"=>"host1.example.com"},
{"port"=>20000, "host"=>"host2.example.com"}]}
String
{:connections=>
[{:port=>10000, :host=>"host1.example.com"},
{:port=>20000, :host=>"host2.example.com"}]}
Symbol
if you're using Rails, it is much simpler - you can use a HashWithIndifferentAccess and access the keys both as String and as Symbols:
my_hash.with_indifferent_access
see also:
http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html
Or you can use the awesome "Facets of Ruby" Gem, which contains a lot of extensions to Ruby Core and Standard Library classes.
require 'facets'
> {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
=> {:some=>"thing", :foo=>"bar}
see also:
http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash
Even more terse:
Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
Since Ruby 2.5.0 you can use Hash#transform_keys or Hash#transform_keys!.
{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}
http://api.rubyonrails.org/classes/Hash.html#method-i-symbolize_keys
hash = { 'name' => 'Rob', 'age' => '28' }
hash.symbolize_keys
# => { name: "Rob", age: "28" }
If you are using json, and want to use it as a hash, in core Ruby you can do it:
json_obj = JSON.parse(json_str, symbolize_names: true)
symbolize_names: If set to true, returns symbols for the names (keys) in a JSON object. Otherwise strings are returned. Strings are the default.
Doc: Json#parse symbolize_names
Here's a way to deep symbolize an object
def symbolize(obj)
return obj.inject({}){|memo,(k,v)| memo[k.to_sym] = symbolize(v); memo} if obj.is_a? Hash
return obj.inject([]){|memo,v | memo << symbolize(v); memo} if obj.is_a? Array
return obj
end
I really like the Mash gem.
you can do mash['key'], or mash[:key], or mash.key
A modification to #igorsales answer
class Object
def deep_symbolize_keys
return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
return self.inject([]){|memo,v | memo << v.deep_symbolize_keys; memo} if self.is_a? Array
return self
end
end
params.symbolize_keys will also work. This method turns hash keys into symbols and returns a new hash.
In Rails you can use:
{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!
Converts to:
{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
So many answers here, but the one method rails function is hash.symbolize_keys
This is my one liner for nested hashes
def symbolize_keys(hash)
hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end
In case the reason you need to do this is because your data originally came from JSON, you could skip any of this parsing by just passing in the :symbolize_names option upon ingesting JSON.
No Rails required and works with Ruby >1.9
JSON.parse(my_json, :symbolize_names => true)
You could be lazy, and wrap it in a lambda:
my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }
my_lamb[:a] == my_hash['a'] #=> true
But this would only work for reading from the hash - not writing.
To do that, you could use Hash#merge
my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))
The init block will convert the keys one time on demand, though if you update the value for the string version of the key after accessing the symbol version, the symbol version won't be updated.
irb> x = { 'a' => 1, 'b' => 2 }
#=> {"a"=>1, "b"=>2}
irb> y = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(x)
#=> {"a"=>1, "b"=>2}
irb> y[:a] # the key :a doesn't exist for y, so the init block is called
#=> 1
irb> y
#=> {"a"=>1, :a=>1, "b"=>2}
irb> y[:a] # the key :a now exists for y, so the init block is isn't called
#=> 1
irb> y['a'] = 3
#=> 3
irb> y
#=> {"a"=>3, :a=>1, "b"=>2}
You could also have the init block not update the hash, which would protect you from that kind of error, but you'd still be vulnerable to the opposite - updating the symbol version wouldn't update the string version:
irb> q = { 'c' => 4, 'd' => 5 }
#=> {"c"=>4, "d"=>5}
irb> r = Hash.new { |h,k| h[k.to_s] }.merge(q)
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called
#=> 4
irb> r
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called again, since this key still isn't in r
#=> 4
irb> r[:c] = 7
#=> 7
irb> r
#=> {:c=>7, "c"=>4, "d"=>5}
So the thing to be careful of with these is switching between the two key forms. Stick with one.
Would something like the following work?
new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }
It'll copy the hash, but you won't care about that most of the time. There's probably a way to do it without copying all the data.
a shorter one-liner fwiw:
my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }
How about this:
my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))
# my_hash['key'] => "val"
# my_hash[:key] => "val"
This is for people who uses mruby and do not have any symbolize_keys method defined:
class Hash
def symbolize_keys!
self.keys.each do |k|
if self[k].is_a? Hash
self[k].symbolize_keys!
end
if k.is_a? String
raise RuntimeError, "Symbolizing key '#{k}' means overwrite some data (key :#{k} exists)" if self[k.to_sym]
self[k.to_sym] = self[k]
self.delete(k)
end
end
return self
end
end
The method:
symbolizes only keys that are String
if symbolize a string means to lose some informations (overwrite part of hash) raise a RuntimeError
symbolize also recursively contained hashes
return the symbolized hash
works in place!
The array we want to change.
strings = ["HTML", "CSS", "JavaScript", "Python", "Ruby"]
Make a new variable as an empty array so we can ".push" the symbols in.
symbols = [ ]
Here's where we define a method with a block.
strings.each {|x| symbols.push(x.intern)}
End of code.
So this is probably the most straightforward way to convert strings to symbols in your array(s) in Ruby. Make an array of strings then make a new variable and set the variable to an empty array. Then select each element in the first array you created with the ".each" method. Then use a block code to ".push" all of the elements in your new array and use ".intern or .to_sym" to convert all the elements to symbols.
Symbols are faster because they save more memory within your code and you can only use them once. Symbols are most commonly used for keys in hash which is great. I'm the not the best ruby programmer but this form of code helped me a lot.If anyone knows a better way please share and you can use this method for hash too!
If you would like vanilla ruby solution and as me do not have access to ActiveSupport here is deep symbolize solution (very similar to previous ones)
def deep_convert(element)
return element.collect { |e| deep_convert(e) } if element.is_a?(Array)
return element.inject({}) { |sh,(k,v)| sh[k.to_sym] = deep_convert(v); sh } if element.is_a?(Hash)
element
end
Starting on Psych 3.0 you can add the symbolize_names: option
Psych.load("---\n foo: bar")
# => {"foo"=>"bar"}
Psych.load("---\n foo: bar", symbolize_names: true)
# => {:foo=>"bar"}
Note: if you have a lower Psych version than 3.0 symbolize_names: will be silently ignored.
My Ubuntu 18.04 includes it out of the box with ruby 2.5.1p57
ruby-1.9.2-p180 :001 > h = {'aaa' => 1, 'bbb' => 2}
=> {"aaa"=>1, "bbb"=>2}
ruby-1.9.2-p180 :002 > Hash[h.map{|a| [a.first.to_sym, a.last]}]
=> {:aaa=>1, :bbb=>2}
This is not exactly a one-liner, but it turns all string keys into symbols, also the nested ones:
def recursive_symbolize_keys(my_hash)
case my_hash
when Hash
Hash[
my_hash.map do |key, value|
[ key.respond_to?(:to_sym) ? key.to_sym : key, recursive_symbolize_keys(value) ]
end
]
when Enumerable
my_hash.map { |value| recursive_symbolize_keys(value) }
else
my_hash
end
end
I like this one-liner, when I'm not using Rails, because then I don't have to make a second hash and hold two sets of data while I'm processing it:
my_hash = { "a" => 1, "b" => "string", "c" => true }
my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key) }
my_hash
=> {:a=>1, :b=>"string", :c=>true}
Hash#delete returns the value of the deleted key
Facets' Hash#deep_rekey is also a good option, especially:
if you find use for other sugar from facets in your project,
if you prefer code readability over cryptical one-liners.
Sample:
require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey
In ruby I find this to be the most simple and easy to understand way to turn string keys in hashes to symbols :
my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}
For each key in the hash we call delete on it which removes it from the hash (also delete returns the value associated with the key that was deleted) and we immediately set this equal to the symbolized key.
Similar to previous solutions but written a bit differently.
This allows for a hash that is nested and/or has arrays.
Get conversion of keys to a string as a bonus.
Code does not mutate the hash been passed in.
module HashUtils
def symbolize_keys(hash)
transformer_function = ->(key) { key.to_sym }
transform_keys(hash, transformer_function)
end
def stringify_keys(hash)
transformer_function = ->(key) { key.to_s }
transform_keys(hash, transformer_function)
end
def transform_keys(obj, transformer_function)
case obj
when Array
obj.map{|value| transform_keys(value, transformer_function)}
when Hash
obj.each_with_object({}) do |(key, value), hash|
hash[transformer_function.call(key)] = transform_keys(value, transformer_function)
end
else
obj
end
end
end

Resources