2

通常、XMLまたはJSONを解析すると、ハッシュ、配列、またはそれらの組み合わせが返されます。多くの場合、無効な配列を解析すると、あらゆる種類のTypeErrors、NoMethodErrors、予期しないnilなどが発生します。

たとえば、responseオブジェクトがあり、次の要素を検索したいとします。

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

応答が

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

NoMethodError私が見たいのは。だけの場合、例外がスローされますnil

多くのnilチェック、レスキュー、またはRailsメソッドに頼らずに要素を探す簡単な方法はありtryますか?

4

6 に答える 6

1

ハッシュドキュメントとファセットの両方を調べようとしましたが、私が見る限り何も目立ちませんでした。

したがって、独自のソリューションを実装することをお勧めします。1つのオプションがあります:

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 に答える
1

キャスパーは私の直前で、同じアイデアを使用していました(どこで見つけたのかわかりませんが、少し前です)が、私のソリューションはより頑丈だと思います

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

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

参考までに、私が知っているいくつかのプロジェクトがあり、可能性に直面してメソッドを連鎖させるというより一般的な問題に取り組んでいますnils

過去にもかなりの議論がありました:

そうは言っても、連鎖Hash#[]アクセスのより具体的な問題については、すでに提供されている回答でおそらく十分です。

于 2013-02-05T11:44:33.333 に答える
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

ハッシュのインスタンスで呼び出されると(配列は他のすべてのクラスと同様にOKであり、#[]定義されています)、このメソッドは、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 []"
于 2013-02-05T06:09:39.140 に答える
0

Ruby 2.3以降、答えはdig

于 2019-11-26T22:15:42.273 に答える