Reputation: 71
Say I have the below ruby hash nested
hash_or_array = [{
"book1" => "buyer1",
"book2" => {
"book21" => "buyer21", "book22" => ["buyer23", "buyer24", true]
},
"book3" => {
"0" => "buyer31", "1" => "buyer32", "2" => "buyer33",
"3" => [{
"4" => "buyer34",
"5" => [10, 11],
"6" => [{
"7" => "buyer35"
}]
}]
},
"book4" => ["buyer41", "buyer42", "buyer43", "buyer35"],
"book5" => {
"book5,1" => "buyer5"
}
}]
And I want to look for the string buyer35
. Upon match, it should return the following
[
{
"book3" => {
"0" => "buyer31", "1" => "buyer32", "2" => "buyer33",
"3" => [{
"4" => "buyer34",
"5" => [10, 11],
"6" => [{
"7" => "buyer35"
}]
}]
}
},
{
"book4" => ["buyer41", "buyer42", "buyer43", "buyer35"]
}
]
The solution below (from another SO question, link below), returns the first match, but I am trying to figure out how to return multiple matches
def recurse(obj, target)
case obj
when Array
obj.each do |e|
case e
when Array, Hash
rv = recurse(e, target)
return [rv] unless rv.nil?
when target
return e
end
end
when Hash
obj.each do |k,v|
case v
when Array, Hash
rv = recurse(v, target)
return {k=>rv} unless rv.nil?
when target
return {k=>v}
end
end
end
nil
end
This is the original question and answer: How to search nested hash of arrays and arrays of hash and only return matching object from the parent node?
UPDATE: The correct return format should be
[
{
"book3" => {
"3" => [{
"6" => [{
"7" => "buyer35"
}]
}]
}
},
{
"book4" => ["buyer41", "buyer42", "buyer43", "buyer35"]
}
]
Upvotes: 0
Views: 667
Reputation: 16748
Here is a recursive solution to your "real" question. Since it mutates the original object, I used a "trick" to make a deep copy first. The keep_entries_with
produces an object with the original shape as your input, but since your output shape is different the second step just transforms the filtered result into the shape of your desired output.
deep_copy = Marshal.load(Marshal.dump(hash_or_array))
def keep_entries_with(target, obj)
return unless obj.is_a?(Hash) || obj.is_a?(Array)
case obj
when Hash
keep_entries_with(target, obj.values)
obj.keep_if do |k, v|
v == target ||
v.is_a?(Hash) && v.values.any? { _1 == target || _1.is_a?(Hash) || _1.is_a?(Array) } ||
v.is_a?(Array) && v.any? { _1 == target || _1.is_a?(Hash) || _1.is_a?(Array) }
end
when Array
obj.each do |v|
keep_entries_with(target, v)
end
end
end
filtered = keep_entries_with("buyer35", deep_copy)
final_result = filtered.first.map { |k, v| { k => v } }
pp final_result
which produces:
[{"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}},
{"book4"=>["buyer41", "buyer42", "buyer43", "buyer35"]}]
Upvotes: 1
Reputation: 16748
Here is a function that recursively searches for the target value in any nested arrays or hashes. The function is then used to select the entries in your top level hash_or_array for entries that contain the target and adds them to an array.
require 'pp'
def find_value(obj, target, found = false)
found = true if obj == target
return found unless obj.is_a?(Array) || obj.is_a?(Hash)
case obj
when Hash
obj.each { |k, v| found = find_value(v, target, found) }
when Array
obj.each { |v| found = find_value(v, target, found) }
end
found
end
found_entries = hash_or_array.inject([]) {|entries, obj| entries << obj.select { |k, v| find_value({ k => v }, "buyer35") }}
pp found_entries
=>
[{"book3"=>
{"0"=>"buyer31",
"1"=>"buyer32",
"2"=>"buyer33",
"3"=>[{"4"=>"buyer34", "5"=>[10, 11], "6"=>[{"7"=>"buyer35"}]}]},
"book4"=>["buyer41", "buyer42", "buyer43", "buyer35"]}]
Upvotes: 2
Reputation: 419
The code below appears to generate the desired output for this specific case:
hash_or_array.inject([]) do |result, x|
x.keep_if { |k, v| v.to_s =~ /buyer35/ }
result << x
end
Upvotes: 1