Eliminating nested each I have an unusual data structure that I need to search - the structure is the result of parsing a JSON file returned from an HTTP request.

The top-level object is an Array, but from top to bottom I have Array-&gt;Hash-&gt;Array-&gt;Hash.

I'm writing code to check the value of a given key in the innermost hash, across the entire data structure.

As it stands, my code is like this:

json = JSON.parse(File.read(file))
json.each do |hash1|
  hash1.keys.each do |key|
    hash1[key].each do |inner_hash|
      # search in the inner hash
    end
  end
end


I know this is ugly. I know there are better ways to iterate over collections and to combine selections together. I'm just not sure what I should do in this instance given the repeated sliding from array to hash.

What I'm Searching

In the above code, I have the search snipped out. My goal with this iteration structure is to skim through entries (the hashes at the entry_id level) for various conditions and either immediately act on or return the Hash object when the conditions are met. For example, if the condition is that field1 contains something, I might want to return 

[
  {
    "entry_id": 544,
    "field1": "something",
    "field2": "something else",
    "field3": 456
  },
  {
    "entry_id": 546,
    "field1": "something!",
    "field2": "something else!",
    "field3": 012
  }
]


Example of Data Structure

[
{
  "12345": [
    {
      "entry_id": 543,
      "field1": "value",
      "field2": "other value",
      "field3": 123
    },
    {
      "entry_id": 544,
      "field1": "something",
      "field2": "something else",
      "field3": 456
    }
  ],
  "23456": [
    {
      "entry_id": 545,
      "field1": "new value",
      "field2": "other new value",
      "field3": 789
    },
    {
      "entry_id": 546,
      "field1": "something!",
      "field2": "something else!",
      "field3": 012
    }
  ]
}
]
You can iterate over the hashes/arrays using a custom enumerator (modified from a StackOverflow answer here):

def dfs(obj, &amp;blk)
  return enum_for(:dfs, obj) unless blk      
  yield obj if obj.is_a? Hash
  if obj.is_a?(Hash) || obj.is_a?(Array)
    obj.each do |*a|
      dfs(a.last, &amp;blk)
    end
  end
end


You can then use this enumerator builder method in any number of other helper methods for whatever you need. For example, to perform your example search, you could define:

def find_node_with_value(obj, key, value)
  dfs(obj).select do |node|
    node[key].respond_to?(:include?) &amp;&amp; node[key].include?(value)
  end
end


And then use it like:

find_node_with_value(json_data, "field1", "something")
# [{"entry_id"=&gt;544, "field1"=&gt;"something" ...}, {"entry_id"=&gt;546, "field1"=&gt;"something!" ...}]