解析哈希和数组的简便方法

时间:2013-02-04 21:42:46

标签: ruby

通常,解析XML或JSON会返回散列,数组或它们的组合。通常,解析无效数组会导致各种TypeError s,NoMethodError s,意外的nils等。

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

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

如果回复是

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

它会抛出NoMethodError异常,当我想要的只是nil时。

有没有一种简单的方法可以在不使用大量的nil检查,救援或Rails try方法的情况下查找元素?

6 个答案:

答案 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