DEV Community

ohaddahan
ohaddahan

Posted on

Nested data structure extractors

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

input_data = {
  x: {
    z: {
      first_name: 'hello',
      last_name: 'world'
    }
  },
  q: [
    1,
    2,
    {
      z: {
        first_name: 'hello',
        last_name: 'bye'
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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):



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

Enter fullscreen mode Exit fullscreen mode

Usage:

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"
    }
]
Enter fullscreen mode Exit fullscreen mode

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):


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 

Enter fullscreen mode Exit fullscreen mode

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):


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"
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)