Alfredo Motta

Introducing Hash#dig_and_collect, a useful extension to the Ruby Hash#dig method

In this blog post I will introduce Hash#dig_and_collect , a simple utility method that is built on top of Hash#dig to help you navigate nested hashes mixed up with arrays.

Why Hash#dig is great

The introduction of the  Hash#dig1 method in Ruby 2.3 completely changed the way I navigate deeply nested hashes. For example, given this Hash :

to get the latitude and longitude you need to do the following:

which is not only weird, but also verbose. Since Ruby 2.3 you can finally write:

which is, needless to say, awesome. However, I started wondering if it was possible to stretch the idea of Hash#dig even further when you have to deal with Hashes that also have Arrays within them.

When Hash#dig is not enough?

In the previous example the addresses key contains a Hash, but this is often a soft guarantee. This means that when multiple addresses are present you may actually find an  Array instead. For example:

What’s the problem here? Now not only addresses could be  nil or not, but depending if the user has one address or multiple addresses the value of the :addresses key could be a Hash  or an Array . This is often the reality of many real-world hashes, and even though we could argue that the data structure design is wrong, somehow we have to deal with it.

A simple solution

Let’s assume that we want to collect all the latitude values of our client  addresses. Hash#dig will not work in this case simply because it doesn’t know what to do as soon as an Array  is found:

The code will have to fetch the :addresses  key. Then if it’s a Hash use dig  to get the :latitude  from the location key. If it is an Array  it should iterate over all the addresses and collect the :latitude  from the various locations. Here it is:

And these are three simple tests that prove that it works:

Introducing Hash#dig_and_collect

I don’t know about you, but I hate the previous code. How can we make it better? Being inspired by Hash#dig what we need here is a good default for navigating deeper our Hash  when we find an Array . I strongly feel a good solution is to collect all the values, exactly like we did in our naïve solution.

Going back to our example, when a client does not have any addresse the code should return an empty array, there was nothing to collect.

The scenarios in which a client has just one address (  addresses  key is a Hash ) or multiple addresses (  addresses  key is an Array ) should now be straightforward:

The implementation is fairly simple and you can read it on Github. We are monkey patching the Hash object, but I am sure it could be done better. Suggestions that involves Ruby refinements are more than welcome.

The solution recursively check what type of key you are trying to fetch and depending on the type recursively call dig_and_collect  either on a Hash  directly, or on all the elements in the Array  that you found along your path.

The rest of the code with the specs is available in this Github repo and I would really love to hear your feedback.


In this blog I presented Hash#dig_and_collect , a simple utility method that is built on top of Hash#dig to help you navigate complex nested hashes. I found it really handy when dealing with badly designed Hashes out in the wild but I feel it could be helpful in other scenarios. Looking forward to hearing your thoughts.

If you enjoyed this blog post you can also follow me on twitter.


  1. Ruby Hash#dig documentation


  1. Brian Katzung

    There’s also Gem XKeys, which has allowed you to read and write nested arrays and hashes since before #dig. You can do things like:

    section = [:system, :users] # How about a (partial?) variable path?
    info[*section, 271, :accesses, :else => 0] += 1 # Default 0, not nil, to increment
    info[:system, :users, :[]] = new_user_info # Push (if [:users] is array)

Leave a Reply:

Your email address will not be published. Required fields are marked *

Answer the question * Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.