2

通常,解析 XML 或 JSON 会返回哈希、数组或它们的组合。通常,解析无效数组会导致各种TypeErrors、NoMethodErrors、意外 nil 等。

例如,我有一个response对象,想查找以下元素:

response['cars'][0]['engine']['5L']

如果响应是

{ 'foo' => { 'bar' => [1, 2, 3] } }

当我只想NoMethodError看到的是nil.

有没有一种简单的方法来查找元素,而无需使用大量的 nil 检查、救援或 Railstry方法?

4

6 回答 6

1

为了参考起见,我知道有几个项目可以解决更普遍的链接方法问题nils

过去也有相当多的讨论:

话虽如此,已经提供的答案可能足以解决更具体的链接Hash#[]访问问题。

于 2013-02-05T11:44:33.333 回答
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, {})}
于 2013-02-05T02:18:11.493 回答
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
于 2013-02-04T22:19:10.830 回答
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
于 2013-02-04T22:15:29.940 回答
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/异常。要使用安全值检索:

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 []"
于 2013-02-05T06:09:39.140 回答
0

Since Ruby 2.3, the answer is dig

于 2019-11-26T22:15:42.273 回答