46

I'm working a little utility written in ruby that makes extensive use of nested hashes. Currently, I'm checking access to nested hash elements as follows:

structure = { :a => { :b => 'foo' }}

# I want structure[:a][:b]

value = nil

if structure.has_key?(:a) && structure[:a].has_key?(:b) then
  value = structure[:a][:b]
end

Is there a better way to do this? I'd like to be able to say:

value = structure[:a][:b]

And get nil if :a is not a key in structure, etc.

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
Paul Morie
  • 15,528
  • 9
  • 52
  • 57
  • 2
    Ruby 2.3 added `Hash#dig` to solve exactly this problem. See my answer below. – user513951 Jan 06 '16 at 03:03
  • 1
    There should be an SO badge for marking a five year old question as a duplicate, and another for having a five-year old question tagged as such. Achievement unlocked! – Paul Morie Feb 14 '16 at 17:38
  • If you are using a Ruby before 2.3, (structure[:a] || {})[:b] should should do the trick – Henok T Mar 18 '17 at 21:02
  • Instead of using`Hash#dig`, I will go idiomatic with @PaulMorie answer, and use the safe navigation operator `.&` — like in `h&.fetch(:a,nil).&fetch(:b,nil)`, navigating blindly through nested hashes or mixed structures which can have not existing (nil) intermediate keys. This kind of structures should be avoided anyway. Both `dig` and `.&` methods were introduced in Ruby 2.3.0 – Claudio Floreani Oct 07 '22 at 10:27

15 Answers15

63

Traditionally, you really had to do something like this:

structure[:a] && structure[:a][:b]

However, Ruby 2.3 added a method Hash#dig that makes this way more graceful:

structure.dig :a, :b # nil if it misses anywhere along the way

There is a gem called ruby_dig that will back-patch this for you.

alexandre-rousseau
  • 2,321
  • 26
  • 33
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
  • 3
    I think you should remove the bit about the top-level hash, since this won't apply arbitrarily deep, so `h[:foo][:bar][:jim]` will still blow up. – Phrogz Apr 04 '11 at 22:20
  • Another gotcha with the default hash value is that it is not persistable. If you dump your data structure to disk and load it later it will loose it's default state, same thing if you send it over the wire. Unlike other classes that explicitly raise errors when this happens Marshal.dump(Hash.new(foo)) will succeed happily loosing your default value. – John F. Miller Apr 04 '11 at 22:38
  • Aren't you introducing a bit of reference problem with `Hash.new({})`? Won't every default entry end up using the same hash? – mu is too short Apr 04 '11 at 23:03
  • 1
    Yes, every default will use the same empty hash, but that's OK. `h[:new_key] = new_value` will create a new entry, and won't modify the default value. – DigitalRoss Apr 04 '11 at 23:28
  • 4
    But if you start doing things like `h = Hash.new({}); h[:a][:b] = 1; h[:c][:d] = 2` you're in for a mess of confusion. – mu is too short Apr 04 '11 at 23:32
  • That would be bad, si. It gives you multi-level reads but takes away multi-level writes in most cases. It's perhaps a bit too dangerous as a general idea. – DigitalRoss Apr 05 '11 at 05:58
  • this should be the accepted answer – Josh Mar 30 '17 at 14:53
  • @Josh FYI the reason it is not is that this answer was edited to add the correct answer at a much later date – user513951 Sep 11 '17 at 14:44
46

Hash and Array have a method called dig.

value = structure.dig(:a, :b)

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 install a gem such as ruby_dig or hash_dig_and_collect, or implement this functionality 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
user513951
  • 12,445
  • 7
  • 65
  • 82
32

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=>{}}}}
Paul Morie
  • 15,528
  • 9
  • 52
  • 57
  • 1
    Hi Paul, can you help me understand how the last one works? I posted a separate question regarding this: http://stackoverflow.com/q/20158213/273333 Thanks! – unpangloss Nov 23 '13 at 03:44
  • @mdm414ZX I've edited my answer to provide more details. Hope that helps. – Paul Morie Nov 25 '13 at 18:28
  • For noobs like myself that want to use this but don't know how to have a default value for the sub-hashes, read [this](http://stackoverflow.com/a/30037418/3399416). Don't trip on the same stone... – JoseHdez_2 May 04 '15 at 18:43
  • Ruby 2.3.0 added `Hash#dig` to solve this problem. See my answer. – user513951 Jan 06 '16 at 03:04
  • Nice, much better than my `h = eval("Hash.new { |v| v || " * 21 + " Hash.new { } " + "} " * 21)` – Dorian Sep 30 '16 at 13:10
  • If you want a counter, you can do `h = Hash.new { |h,k| h[k] = Hash.new(0) }` – dawg Jan 27 '21 at 13:24
  • @ user513951 this answer, not yours, is the idiomatic way to use nested hashes in Ruby. You only need dig when navigating blindly through nested hashes or mixed structures which can have not existing (nil) intermediate keys. This can be overcome starting from Ruby 2.3.0 combining the safe navigation operator (.&) and fetch(…,nil) — like in `h&.fetch(:a,nil).&fetch(:b,nil)` @CarySwoveland no, this is wrong. Learn how Proc works in the details of this answer. – Claudio Floreani Oct 07 '22 at 10:21
  • @ClaudioFloreani, I was just a kid when I wrote that so many years ago... – Cary Swoveland Oct 07 '22 at 17:24
14

I made rubygem for this. Try vine.

Install:

gem install vine

Usage:

hash.access("a.b.c")
lulalala
  • 17,572
  • 15
  • 110
  • 169
Cheng
  • 4,816
  • 4
  • 41
  • 44
  • Fantastic, I'm calling SharePoint web services using Savon (which is great) and my response is the following using Vine... ;-) data.vine("get_user_collection_from_group_response.get_user_collection_from_group_result.get_user_collection_from_group.users.user") – Marc Sep 12 '13 at 18:44
  • 1
    Vine seems interesting, but you may also want to check out Hashie, which is a more complete library. See my answer. – Javid Jamae Dec 18 '13 at 23:01
  • 1
    As an aside this really is only good for accessing elements in the hash. Don't expect to be able to replace the values using this method. – Tom Mar 05 '14 at 12:43
7

I think one of the most readable solutions is using Hashie:

require 'hashie'
myhash = Hashie::Mash.new({foo: {bar: "blah" }})

myhash.foo.bar
=> "blah"    

myhash.foo?
=> true

# use "underscore dot" for multi-level testing
myhash.foo_.bar?
=> true
myhash.foo_.huh_.what?
=> false
Javid Jamae
  • 8,741
  • 4
  • 47
  • 62
3
value = structure[:a][:b] rescue nil
fl00r
  • 82,987
  • 33
  • 217
  • 237
  • 2
    This will silently turn missing variables or methods, among other things, into nil. In a way that's the intent, but this is a broad knife for what perhaps should be a fine cut. – Wayne Conrad Apr 04 '11 at 22:19
  • I'm very cautious about using `rescue` like that, much for the same reason that Wayne gives, but also because it can mask errors in logic or syntax that you should know about. Finding errors masked this way can be tough. – the Tin Man Apr 05 '11 at 04:19
2

You could just build a Hash subclass with an extra variadic method for digging all the way down with appropriate checks along the way. Something like this (with a better name of course):

class Thing < Hash
  def find(*path)
    path.inject(self) { |h, x| return nil if(!h.is_a?(Thing) || h[x].nil?); h[x] }
  end
end

Then just use Things instead of hashes:

>> x = Thing.new
=> {}
>> x[:a] = Thing.new
=> {}
>> x[:a][:b] = 'k'
=> "k"
>> x.find(:a)
=> {:b=>"k"}
>> x.find(:a, :b)
=> "k"
>> x.find(:a, :b, :c)
=> nil
>> x.find(:a, :c, :d)
=> nil
mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Why don't you get a local jump error from that return? Your code definitely works. I would think this is roughly equivalent: >> hash = {:a => {:b => 'k'}} >> [:a, :b].inject(hash) {|h, x| return nil if (h[x].nil? || !h[x].is_a?(Hash)); h[x] }, but gives a LocalJumpError – rainkinz Dec 30 '13 at 14:52
  • 1
    @rainkinz: You're getting a LocalJumpError because your block is trying to return without being inside a method (or "a method which declared the block" to be pedantic). My `return` works because it is returning from the `find` method, your version doesn't have anywhere to return from so Ruby throws a hissy fit. – mu is too short Dec 30 '13 at 18:20
2

Solution 1

I suggested this in my question before:

class NilClass; def to_hash; {} end end

Hash#to_hash is already defined, and returns self. Then you can do:

value = structure[:a].to_hash[:b]

The to_hash ensures that you get an empty hash when the previous key search fails.

Solution2

This solution is similar in spirit to mu is too short's answer in that it uses a subclass, but still somewhat different. In case there is no value for a certain key, it does not use a default value, but rather creates a value of empty hash, so that it does not have the problem of confusion in assigment that DigitalRoss's answer has, as was pointed out by mu is too short.

class NilFreeHash < Hash
  def [] key; key?(key) ? super(key) : self[key] = NilFreeHash.new end
end

structure = NilFreeHash.new
structure[:a][:b] = 3
p strucrture[:a][:b] # => 3

It departs from the specification given in the question, though. When an undefined key is given, it will return an empty hash instread of nil.

p structure[:c] # => {}

If you build an instance of this NilFreeHash from the beginning and assign the key-values, it will work, but if you want to convert a hash into an instance of this class, that may be a problem.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • Why not combine both your answers into one? – the Tin Man Apr 05 '11 at 04:20
  • They are unrelated, and I though it's better for them to be separated. Are you suggesting to put two solutions in one post, or combining them to give a different answer? – sawa Apr 05 '11 at 04:22
  • Put them both in one answer. That's a very common practice on SO, especially when you compare/contrast them. – the Tin Man Apr 05 '11 at 04:34
  • Cool. *I* find it nice that way because it's a lot easier to see the differences in the approaches. That's a lot harder when they're separate answers. – the Tin Man Apr 05 '11 at 04:51
  • I see. I guess I am still learning know about the custom here. – sawa Apr 05 '11 at 04:55
1

The XKeys gem will read and auto-vivify-on-write nested hashes (::Hash) or hashes and arrays (::Auto, based on the key/index type) with a simple, clear, readable, and compact syntax by enhancing #[] and #[]=. The sentinel symbol :[] will push onto the end of an array.

require 'xkeys'

structure = {}.extend XKeys::Hash
structure[:a, :b] # nil
structure[:a, :b, :else => 0] # 0 (contextual default)
structure[:a] # nil, even after above
structure[:a, :b] = 'foo'
structure[:a, :b] # foo
Brian K
  • 114
  • 4
1

This monkey patch function for Hash should be easiest (at least for me). It also doesn't alter structure i.e. changing nil's to {}. It would still also apply even if you're reading a tree from a raw source e.g. JSON. It also doesn't need to produce empty hash objects as it goes or parse a string. rescue nil was actually a good easy solution for me as I'm brave enough for such a low risk but I find it to essentially have a drawback with performance.

class ::Hash
  def recurse(*keys)
    v = self[keys.shift]
    while keys.length > 0
      return nil if not v.is_a? Hash
      v = v[keys.shift]
    end
    v
  end
end

Example:

> structure = { :a => { :b => 'foo' }}
=> {:a=>{:b=>"foo"}}

> structure.recurse(:a, :b)
=> "foo"

> structure.recurse(:a, :x)
=> nil

What's also good is that you can play around saved arrays with it:

> keys = [:a, :b]
=> [:a, :b]

> structure.recurse(*keys)
=> "foo"

> structure.recurse(*keys, :x1, :x2)
=> nil
konsolebox
  • 72,135
  • 12
  • 99
  • 105
0

In my case, I needed a two-dimensional matrix where each cell is a list of items.

I found this technique which seems to work. It might work for the OP:

$all = Hash.new()

def $all.[](k)
  v = fetch(k, nil)
  return v if v

  h = Hash.new()
  def h.[](k2)
    v = fetch(k2, nil)
    return v if v
    list = Array.new()
    store(k2, list)
    return list
  end

  store(k, h)
  return h
end

$all['g1-a']['g2-a'] << '1'
$all['g1-a']['g2-a'] << '2'

$all['g1-a']['g2-a'] << '3'
$all['g1-a']['g2-b'] << '4'

$all['g1-b']['g2-a'] << '5'
$all['g1-b']['g2-c'] << '6'

$all.keys.each do |group1|
  $all[group1].keys.each do |group2|
    $all[group1][group2].each do |item|
      puts "#{group1} #{group2} #{item}"
    end
  end
end

The output is:

$ ruby -v && ruby t.rb
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-linux]
g1-a g2-a 1
g1-a g2-a 2
g1-a g2-a 3
g1-a g2-b 4
g1-b g2-a 5
g1-b g2-c 6
JohnA
  • 699
  • 2
  • 7
  • 15
0

I am currently trying out this:

# --------------------------------------------------------------------
# System so that we chain methods together without worrying about nil
# values (a la Objective-c).
# Example:
#   params[:foo].try?[:bar]
#
class Object
  # Returns self, unless NilClass (see below)
  def try?
    self
  end
end  
class NilClass
  class MethodMissingSink
    include Singleton
    def method_missing(meth, *args, &block)
    end
  end
  def try?
    MethodMissingSink.instance
  end
end

I know the arguments against try, but it is useful when looking into things, like say, params.

Jaime Cham
  • 1,494
  • 1
  • 15
  • 16
0

You can use the andand gem, but I'm becoming more and more wary of it:

>> structure = { :a => { :b => 'foo' }} #=> {:a=>{:b=>"foo"}}
>> require 'andand' #=> true
>> structure[:a].andand[:b] #=> "foo"
>> structure[:c].andand[:b] #=> nil
Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
0

There is the cute but wrong way to do this. Which is to monkey-patch NilClass to add a [] method that returns nil. I say it is the wrong approach because you have no idea what other software may have made a different version, or what behavior change in a future version of Ruby can be broken by this.

A better approach is to create a new object that works a lot like nil but supports this behavior. Make this new object the default return of your hashes. And then it will just work.

Alternately you can create a simple "nested lookup" function that you pass the hash and the keys to, which traverses the hashes in order, breaking out when it can.

I would personally prefer one of the latter two approaches. Though I think it would be cute if the first was integrated into the Ruby language. (But monkey-patching is a bad idea. Don't do that. Particularly not to demonstrate what a cool hacker you are.)

btilly
  • 43,296
  • 3
  • 59
  • 88
  • 1
    Aww, I've never been called cute before. :) I do agree that this is convenient but ultimately too dangerous to use. – Phrogz Apr 04 '11 at 22:18
0

Not that I would do it, but you can Monkeypatch in NilClass#[]:

> structure = { :a => { :b => 'foo' }}
#=> {:a=>{:b=>"foo"}}

> structure[:x][:y]
NoMethodError: undefined method `[]' for nil:NilClass
        from (irb):2
        from C:/Ruby/bin/irb:12:in `<main>'

> class NilClass; def [](*a); end; end
#=> nil

> structure[:x][:y]
#=> nil

> structure[:a][:y]
#=> nil

> structure[:a][:b]
#=> "foo"

Go with @DigitalRoss's answer. Yes, it's more typing, but that's because it's safer.

Phrogz
  • 296,393
  • 112
  • 651
  • 745