Ruby - Access multidimensional hash and avoid access nil object
There are many approaches to this.
If you use Ruby 2.3 or above, you can use dig
my_hash.dig('key1', 'key2', 'key3')
Plenty of folks stick to plain ruby and chain the &&
guard tests.
You could use stdlib Hash#fetch too:
my_hash.fetch('key1', {}).fetch('key2', {}).fetch('key3', nil)
Some like chaining ActiveSupport's #try method.
my_hash.try(:[], 'key1').try(:[], 'key2').try(:[], 'key3')
Others use andand
myhash['key1'].andand['key2'].andand['key3']
Some people think egocentric nils are a good idea (though someone might hunt you down and torture you if they found you do this).
class NilClass
def method_missing(*args); nil; end
end
my_hash['key1']['key2']['key3']
You could use Enumerable#reduce (or alias inject).
['key1','key2','key3'].reduce(my_hash) {|m,k| m && m[k] }
Or perhaps extend Hash or just your target hash object with a nested lookup method
module NestedHashLookup
def nest *keys
keys.reduce(self) {|m,k| m && m[k] }
end
end
my_hash.extend(NestedHashLookup)
my_hash.nest 'key1', 'key2', 'key3'
Oh, and how could we forget the maybe monad?
Maybe.new(my_hash)['key1']['key2']['key3']
is there a convenience way to build nested hash in ruby
I agree with sawa's comment. You will probably have many other problems with such approach. However, what you want is possible.
Search for "ruby hash default value".
By default, the default value of Hash entry is nil
. You can change that, ie:
h = Hash.new(5)
and now the h
when asked for non-existing key will return 5, not nil. You can order it to return a new empty array, or new empty hash in a similar way.
But, be careful. It is easy to accidentally SHARE the default instance through all entries.
h = Hash.new([]) // default value = Array object, let's name it X
one = h[:dad] // returns THE SAME object X
two = h[:mom] // returns THE SAME object X
You must be careful to not use the shared-default-instance, and to use operations that will not mutate it. You cannot just
h[:mom] << 'thing'
as the h[:brandnewone]
will now return mutated default instance with "thing" inside.
See here for a good explanation and proper usage examples
or, even better: example of autovivifying hash
Safe navigation equivalent to Rails try for hashes
&.
is not equivalent to Rails' try
, but you can use &.
for hashes. Just use it, nothing special.
hash[:key1]&.[](:key2)&.[](:key3)
Although I would not do that.
Is there a clean way to avoid calling a method on nil in a nested params hash?
Check Ick's maybe. You don't need to significantly refactor your code, just intersperse maybe proxies when necessary:
params[:subject].maybe[:name]
The same author (raganwald) also wrote andand, with the same idea.
How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?
Ruby 2.3.0 introduced a new method called dig
on both Hash
and Array
that solves this problem entirely.
name = params.dig(:company, :owner, :name)
It returns nil
if the key is missing at any level.
If you are using a version of Ruby older than 2.3, you can use the ruby_dig gem or implement it yourself:
module RubyDig
def dig(key, *rest)
if value = (self[key] rescue nil)
if rest.empty?
value
elsif value.respond_to?(:dig)
value.dig(*rest)
end
end
end
end
if RUBY_VERSION < '2.3'
Array.send(:include, RubyDig)
Hash.send(:include, RubyDig)
end
How can I get the keys in a multidimensional hash in Ruby?
Assuming you have a hash which has one unknown key with one value : a hash.
h = {"<id>"=>{"first_name"=>"test", "last_name"=>"test_l", "username"=>"test_user",
"title"=>"Sales Manager", "office"=>"test", "email"=>"test@test.com"}}
p h[h.keys.first].keys
#=> ["first_name", "last_name", "username", "title", "office", "email"]
(But every time I see a construct like this I wonder why it isn't a Struct).
Elegantly handling blank values in a nested hash
#fetch
is your friend:
my_hash.fetch(:parent, {})[:child].blank?
Accessing elements of nested hashes in ruby
The way I usually do this these days is:
h = Hash.new { |h,k| h[k] = {} }
This will give you a hash that creates a new hash as the entry for a missing key, but returns nil for the second level of key:
h['foo'] -> {}
h['foo']['bar'] -> nil
You can nest this to add multiple layers that can be addressed this way:
h = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = {} } }
h['bar'] -> {}
h['tar']['zar'] -> {}
h['scar']['far']['mar'] -> nil
You can also chain indefinitely by using the default_proc
method:
h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
h['bar'] -> {}
h['tar']['star']['par'] -> {}
The above code creates a hash whose default proc creates a new Hash with the same default proc. So, a hash created as a default value when a lookup for an unseen key occurs will have the same default behavior.
EDIT: More details
Ruby hashes allow you to control how default values are created when a lookup occurs for a new key. When specified, this behavior is encapsulated as a Proc
object and is reachable via the default_proc
and default_proc=
methods. The default proc can also be specified by passing a block to Hash.new
.
Let's break this code down a little. This is not idiomatic ruby, but it's easier to break it out into multiple lines:
1. recursive_hash = Hash.new do |h, k|
2. h[k] = Hash.new(&h.default_proc)
3. end
Line 1 declares a variable recursive_hash
to be a new Hash
and begins a block to be recursive_hash
's default_proc
. The block is passed two objects: h
, which is the Hash
instance the key lookup is being performed on, and k
, the key being looked up.
Line 2 sets the default value in the hash to a new Hash
instance. The default behavior for this hash is supplied by passing a Proc
created from the default_proc
of the hash the lookup is occurring in; ie, the default proc the block itself is defining.
Here's an example from an IRB session:
irb(main):011:0> recursive_hash = Hash.new do |h,k|
irb(main):012:1* h[k] = Hash.new(&h.default_proc)
irb(main):013:1> end
=> {}
irb(main):014:0> recursive_hash[:foo]
=> {}
irb(main):015:0> recursive_hash
=> {:foo=>{}}
When the hash at recursive_hash[:foo]
was created, its default_proc
was supplied by recursive_hash
's default_proc
. This has two effects:
- The default behavior for
recursive_hash[:foo]
is the same asrecursive_hash
. - The default behavior for hashes created by
recursive_hash[:foo]
'sdefault_proc
will be the same asrecursive_hash
.
So, continuing in IRB, we get the following:
irb(main):016:0> recursive_hash[:foo][:bar]
=> {}
irb(main):017:0> recursive_hash
=> {:foo=>{:bar=>{}}}
irb(main):018:0> recursive_hash[:foo][:bar][:zap]
=> {}
irb(main):019:0> recursive_hash
=> {:foo=>{:bar=>{:zap=>{}}}}
Check hash for nil values
I think the problem may be that you're interpolating the hash in the string, which is converting the nil into an empty string, which is not actually nil.
Try:
if tweet['metadata']['geo'].nil?
puts("nil")
else
puts("#{tweet['metadata']['geo']['coordinates']}")
end
Ruby: Unexpected result from Hash.keys in multi-dimensional hash
You just set the default value that will be returned if no value is found. This doesn't change the fact that there is no value assigned to h["foo"]
. {"bar"=>6}
will be the value for any key which is not found.
h = {}
h.default = {} # => {}
h["foo"]["bar"] = 6 # => 6
h["foo"] # => {"bar"=>6}
h["baz"] # => {"bar"=>6}
If you wanted a hash which returns and sets values of missing keys to empty hashes, you have to do:
h = Hash.new { |hash, key| hash[key] = {} }
h["foo"]["bar"] = 6 # => 6
h # => {:foo=>{:bar=>6}}
Related Topics
Why Not Use Shared Activerecord Connections For Rspec + Selenium
Devise Custom Routes and Login Pages
How to Stub Things in Minitest
How to Run Rake Tasks Within a Ruby Script
In Ruby, Is There an Array Method That Combines 'Select' and 'Map'
Getaddrinfo: Nodename Nor Servname Provided, or Not Known
Rails Each Loop Insert Tag Every 6 Items
Checking If a Variable Is Defined in Sass
How to Convert Datetime.Now to Utc in Ruby
How to Remove a Key from Hash and Get the Remaining Hash in Ruby/Rails
Create a Devise User from Ruby Console
Extending Devise Sessionscontroller to Authenticate Using JSON