通常,解析XML或JSON会返回散列,数组或它们的组合。通常,解析无效数组会导致各种TypeError
s,NoMethodError
s,意外的nils等。
例如,我有一个response
对象,想要找到以下元素:
response['cars'][0]['engine']['5L']
如果回复是
{ 'foo' => { 'bar' => [1, 2, 3] } }
它会抛出NoMethodError
异常,当我想要的只是nil
时。
有没有一种简单的方法可以在不使用大量的nil检查,救援或Rails try
方法的情况下查找元素?
答案 0 :(得分:1)
我试图查看Hash文档以及Facets,但是就我所见,没有什么突出的。
所以你可能想要实现自己的解决方案。这是一个选项:
class Hash
def deep_index(*args)
args.inject(self) { |e,arg|
break nil if e[arg].nil?
e[arg]
}
end
end
h1 = { 'cars' => [{'engine' => {'5L' => 'It worked'}}] }
h2 = { 'foo' => { 'bar' => [1, 2, 3] } }
p h1.deep_index('cars', 0, 'engine', '5L')
p h2.deep_index('cars', 0, 'engine', '5L')
p h2.deep_index('foo', 'bonk')
输出:
"It worked"
nil
nil
答案 1 :(得分:1)
Casper就在我面前,他使用了同样的想法(不知道我在哪里发现它,是不久前的)但我相信我的解决方案更坚固
module DeepFetch
def deep_fetch(*keys, &fetch_default)
throw_fetch_default = fetch_default && lambda {|key, coll|
args = [key, coll]
# only provide extra block args if requested
args = args.slice(0, fetch_default.arity) if fetch_default.arity >= 0
# If we need the default, we need to stop processing the loop immediately
throw :df_value, fetch_default.call(*args)
}
catch(:df_value){
keys.inject(self){|value,key|
block = throw_fetch_default && lambda{|*args|
# sneak the current collection in as an extra block arg
args << value
throw_fetch_default.call(*args)
}
value.fetch(key, &block) if value.class.method_defined? :fetch
}
}
end
# Overload [] to work with multiple keys
def [](*keys)
case keys.size
when 1 then super
else deep_fetch(*keys){|key, coll| coll[key]}
end
end
end
response = { 'foo' => { 'bar' => [1, 2, 3] } }
response.extend(DeepFetch)
p response.deep_fetch('cars') { nil } # nil
p response.deep_fetch('cars', 0) { nil } # nil
p response.deep_fetch('foo') { nil } # {"bar"=>[1, 2, 3]}
p response.deep_fetch('foo', 'bar', 0) { nil } # 1
p response.deep_fetch('foo', 'bar', 3) { nil } # nil
p response.deep_fetch('foo', 'bar', 0, 'engine') { nil } # nil
答案 2 :(得分:1)
如果在没有密钥时可以使用空哈希而不是nil
,那么你可以这样做:
response.fetch('cars', {}).fetch(0, {}).fetch('engine', {}).fetch('5L', {})
或通过定义方法Hash#_
来保存某些类型:
class Hash; def _ k; fetch(k, {}) end end
response._('cars')._(0)._('engine')._('5L')
或者这样做:
["cars", 0, "engine", "5L"].inject(response){|h, k| h.fetch(k, {})}
答案 3 :(得分:1)
为了便于参考,我知道有几个项目可以解决面对可能nils
时链接方法的更普遍的问题:
过去也有过相当多的讨论:
话虽如此,已经提供的答案可能足以解决链式Hash#[]
访问的更具体问题。
答案 4 :(得分:0)
我建议一种向我们感兴趣的实例注入自定义#[]
方法的方法:
def weaken_checks_for_brackets_accessor inst
inst.instance_variable_set(:@original_get_element_method, inst.method(:[])) \
unless inst.instance_variable_get(:@original_get_element_method)
singleton_class = class << inst; self; end
singleton_class.send(:define_method, :[]) do |*keys|
begin
res = (inst.instance_variable_get(:@original_get_element_method).call *keys)
rescue
end
weaken_checks_for_brackets_accessor(res.nil? ? inst.class.new : res)
end
inst
end
在Hash实例上调用(Array就像所有其他类一样,定义了#[]
),此方法存储原始Hash#[]
方法,除非它已经被替换(这是需要防止的)多次调用时堆栈溢出。)然后它注入#[]
方法的自定义实现,返回空类实例而不是nil / exception。要使用安全值检索:
a = { 'foo' => { 'bar' => [1, 2, 3] } }
p (weaken_checks_for_brackets_accessor a)['foo']['bar']
p "1 #{a['foo']}"
p "2 #{a['foo']['bar']}"
p "3 #{a['foo']['bar']['ghgh']}"
p "4 #{a['foo']['bar']['ghgh'][0]}"
p "5 #{a['foo']['bar']['ghgh'][0]['olala']}"
产量:
#⇒ [1, 2, 3]
#⇒ "1 {\"bar\"=>[1, 2, 3]}"
#⇒ "2 [1, 2, 3]"
#⇒ "3 []"
#⇒ "4 []"
#⇒ "5 []"
答案 5 :(得分:0)
从Ruby 2.3开始,答案是dig