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)