arraysrubynestedhashmapkeypaths

How to use a key path to traverse a hash map in Ruby?


So I have a nested hash like below:

nested_hash = {
  foo: foo,
  bar: {
    foo: foo,
    bar: {
      foo: foo,
      bar: {
        foo: bar
      }
    }
  }
}

To access the values at different levels, you could use multiple methods, like so:

def one_level(key1)
  nested_hash[key1]
end

def two_levels(key1, key2)
  nested_hash[key1][key2]
end

def three_levels(key1, key2, key3)
  nested_hash[key1][key2][key3]
end

But maybe you need it in one single method like this:

def up_to_three_levels(key1, key2, key3)
  if key1 && key2 && key3
    nested_hash[key1][key2][key3]
  elif key1 && key2
    nested_hash[key1][key2]
  else
    nested_hash[key1]
  end
end

This is obviously not ideal and doesn't scale. It would be nice if I could just pass an array of arbitrary length to reach an arbitrary level. Is there a method that does that?

(For context: The problem I'm trying to solve is that I need to take a key path as a parameter and use it to fetch data from multiple different data structures.)


Solution

  • Yes. It's called dig:

    nested_hash.dig(key1, key2, key3)
    

    It doesn't take an array, but there is the splat operator (*) that lets you convert an array to a list of parameters, like so:

    nested_hash.dig(*[key1, key2, key3])
    

    Not that you would have to implement this yourself, but I think it's useful to know that it's a very simple recursive problem that can be solved with only a few lines of code:

    def dig(collection, keys)
        if keys.length == 0 || collection == nil
            collection
        else
            dig(collection[keys[0]], keys.drop(1))
        end
    end
    

    Also worth noting is that dig also exists on arrays and that you can have a mix of arrays and hash maps in your nested structure and seamlessly traverse it with dig.