web服务返回的散列包含未知数量的嵌套散列,其中一些包含数组,而数组又包含未知数量的嵌套散列。
有些键不是唯一的——即在多个嵌套散列中存在。
然而,所有我真正关心的键都是唯一的
是否有某种方法我可以给顶级哈希一个键,并得到它的值,即使键值对深埋在这个泥潭?
(web服务是Amazon Product Advertising API,它根据结果的数量和每个产品类别中允许的搜索类型稍微改变了结果的结构)
这里有一个简单的递归解决方案:
def nested_hash_value(obj,key)
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=nested_hash_value(a.last,key) }
r
end
end
h = { foo:[1,2,[3,4],{a:{bar:42}}] }
p nested_hash_value(h,:bar)
#=> 42
无需猴子补丁,只需使用Hashie gem: https://github.com/intridea/hashie#deepfind
user = {
name: { first: 'Bob', last: 'Boberts' },
groups: [
{ name: 'Rubyists' },
{ name: 'Open source enthusiasts' }
]
}
user.extend Hashie::Extensions::DeepFind
user.deep_find(:name) #=> { first: 'Bob', last: 'Boberts' }
对于任意可枚举对象,还有另一个扩展可用,DeepLocate: https://github.com/intridea/hashie#deeplocate
结合上面的一些答案和评论:
class Hash
def deep_find(key, object=self, found=nil)
if object.respond_to?(:key?) && object.key?(key)
return object[key]
elsif object.is_a? Enumerable
object.find { |*a| found = deep_find(key, a.last) }
return found
end
end
end
Ruby 2.3引入了hash# dig,它允许您执行以下操作:
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
barelyknown解决方案的一种变体:这将在散列中找到键的所有值,而不是第一个匹配。
class Hash
def deep_find(key, object=self, found=[])
if object.respond_to?(:key?) && object.key?(key)
found << object[key]
end
if object.is_a? Enumerable
found << object.collect { |*a| deep_find(key, a.last) }
end
found.flatten.compact
end
end
{a: [{b: 1}, {b: 2}]}.deep_find(:b)
将返回[1, 2]
尽管这似乎是一个常见的问题,但我刚刚花了一段时间试图找到/提出我所需要的,我认为这与您的要求相同。第一个回复中的两个链接都不正确。
class Hash
def deep_find(key)
key?(key) ? self[key] : self.values.inject(nil) {|memo, v| memo ||= v.deep_find(key) if v.respond_to?(:deep_find) }
end
end
所以给定:
hash = {:get_transaction_list_response => { :get_transaction_list_return => { :transaction => [ { ...
:
hash.deep_find(:transaction)
将查找与:事务键相关联的数组。
这不是最优的,因为即使填充了memo,注入也会继续迭代。
我使用以下代码
def search_hash(hash, key)
return hash[key] if hash.assoc(key)
hash.delete_if{|key, value| value.class != Hash}
new_hash = Hash.new
hash.each_value {|values| new_hash.merge!(values)}
unless new_hash.empty?
search_hash(new_hash, key)
end
end
我最终使用了这个小的树搜索,我写:
def trie_search(str, obj=self)
if str.length <= 1
obj[str]
else
str_array = str.chars
next_trie = obj[str_array.shift]
next_trie ? trie_search(str_array.join, next_trie) : nil
end
end
注意:这只是目前嵌套的哈希。目前不支持数组
因为Rails 5 ActionController::Parameters不再从Hash继承,我不得不修改方法并使其特定于参数。
module ActionController
class Parameters
def deep_find(key, object=self, found=nil)
if object.respond_to?(:key?) && object.key?(key)
return object[key]
elsif object.respond_to?(:each)
object = object.to_unsafe_h if object.is_a?(ActionController::Parameters)
object.find { |*a| found = deep_find(key, a.last) }
return found
end
end
end
end
如果键被找到,它返回该键的值,但它不返回ActionController::Parameter对象,所以强参数不保留