Ruby - Access Multidimensional Hash and Avoid Access Nil Object

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:

  1. The default behavior for recursive_hash[:foo] is the same as recursive_hash.
  2. The default behavior for hashes created by recursive_hash[:foo]'s default_proc will be the same as recursive_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



Leave a reply



Submit