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'
}
}
]
}
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
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"
}
]
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
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"
}
Top comments (0)