I often find myself wanting to find hash keys that are nested within a given hash. This is common when getting API responses, as they usually give back a ton of information. The way to handle these giant hashes usually involves manually traversing each level:

response_hash = # giant API response
value_i_want = response_hash["key1"]["key2"]["key3"]

Or even worse, when the response hash contains arrays as values to hash keys:

response_hash = # giant API response
value_i_want = response_hash["key1"]["key2"].first["key3"]

This requires you to know exactly how the hash is structured, which is a pain. What if you could just specify the key you wanted to find, and the work of traversing the hash could be done for you?

Here is a solution I came up with:

module DeepFind
  # obj is the hash we want to traverse, key is what we're looking for
  def deep_find(obj, key)
    # The base case for our recursive method. Returns if the key is found.
    return obj[key] if obj.respond_to?(:key?) && obj.key?(key)

    # If the object is either a Hash or Array
    if obj.is_a? Enumerable
      found = nil
      # Use the Enumerable#find to return the first object
      # for which the block returns true. Sets the found
      # variable to the result of calling #deep_find again.
      # With this recursive call, we pass in the last nested hash
      # of the current level of nesting as our object. This is
      # essentially a depth first search.
      obj.find { |*a| found = deep_find(a.last, key) }

      # return the value of the key
      found
    end
  end
end

This is works for some cases, but we’re not quite done yet. What if the key you’re looking for is not unique? You could get a response hash that looks like this:

{
  user: {
    telephone: {
      value: "180011111111"
    },
    address: {
      value: "123 Boulevard"
    }
  }
}

If you want to get the value key for address, it won’t work. The value of telephone will be found first, and the method will return.

To fix this, I added an optional nested_key argument, so you can define the parent key first.

module DeepFind
  def deep_find(obj, key, nested_key: nil)
    return obj[key] if obj.respond_to?(:key?) && obj.key?(key)
    if obj.is_a? Enumerable
      found = nil
      obj.find { |*a| found = deep_find(a.last, key) }
      if nested_key.present?
        deep_find(found, nested_key)
      else
        found
      end
    end
  end
end

Now you can find the value key within address by writing deep_find(response_hash, "address", nested_key: "value").

To use deep_find in any Ruby class, just include the DeepFind module:

class ApiInterface
  include DeepFind

  #...
end