Nested data structure extractors
Tags: ruby, rails
Published: 2019-09-21 12:42:56


Wanted to share a few helpful methods I use to traverse and extract data from nested data structures, usually 3rd party API responses.

```ruby
input_data = {
  x: {
    z: {
      first_name: 'hello',
      last_name: 'world'
    }
  },
  q: [
    1,
    2,
    {
      z: {
        first_name: 'hello',
        last_name: 'bye'
      }
    }
  ]
}
```

First extraction method `fetch_all_by_key` , the purpose of this method is to extract all the elements with the same key (for example getting a list of similar objects in a response):

```ruby


def fetch_all_by_key(element: nil, key: nil, container: [])
  if element.is_a?(Array)
    element.each { |i| fetch_all_by_key(element: i, key: key, container: container) }
  elsif element.is_a?(Hash)
    container << element.fetch(key) unless element.fetch(key, nil).nil?
    element.each_pair { |_, v| fetch_all_by_key(element: v, key: key, container: container) }
  end
  container
end

```

Usage:
```ruby
fetch_all_by_key(element: input_data, key: :q)
[
    [0] [
        [0] 1,
        [1] 2,
        [2] {
            :z => {
                :first_name => "hello",
                 :last_name => "bye"
            }
        }
    ]
]


fetch_all_by_key(element: input_data, key: :z)
[
    [0] {
        :first_name => "hello",
         :last_name => "world"
    },
    [1] {
        :first_name => "hello",
         :last_name => "bye"
    }
]
```

The next method, is basically the same as the previous one, but will only fetch the first element which fits our requirement (when you care only about getting any element that fits your key):

```ruby

def deep_fetch(element: nil, key: nil)
  ret_value = tmp = nil
  if element.is_a?(Array)
    element.each do |i|
      tmp = deep_fetch(element: i, key: key)
      break unless tmp.nil?
    end
  elsif element.is_a?(Hash)
    if element.has_key?(key)
      tmp = element[key]
    else
      element.each_pair do |_, v|
        tmp = deep_fetch(element: v, key: key)
        break unless tmp.nil?
      end
    end
  end
  ret_value = tmp unless tmp.nil?
  ret_value
end

deep_fetch(element: input_data, key: :q)
[
    [0] 1,
    [1] 2,
    [2] {
        :z => {
            :first_name => "hello",
             :last_name => "bye"
        }
    }
]

 deep_fetch(element: input_data, key: :z)
{
    :first_name => "hello",
     :last_name => "world"
}
2.6.3 :096 

```


The last method uses the previous method to enforce order (sometimes the same key can appear in two different hierarchical paths hence you want to have more control over what you're fetching):

```ruby

def multi_deep_fetch(element: nil, keys: [])
  while key = keys.shift
    element = deep_fetch(element: element, key: key)
  end
  element
end


multi_deep_fetch(element: input_data, keys: [:q])
[
    [0] 1,
    [1] 2,
    [2] {
        :z => {
            :first_name => "hello",
             :last_name => "bye"
        }
    }
]



 multi_deep_fetch(element: input_data, keys: [:y, :z])
nil

 multi_deep_fetch(element: input_data, keys: [:x, :z])
{
    :first_name => "hello",
     :last_name => "world"
}

multi_deep_fetch(element: input_data, keys: [:q, :z])
{
    :first_name => "hello",
     :last_name => "bye"
}

```