Tratando con muchos en Ruby

En un script de Ruby puro tengo esto:

result = JSON.parse result.body_str count = result && result["ke1"] && result["ke1"]["key2"] && result["ke1"]["key2"]["key3"] && result["ke1"]["key2"]["key3"]["key4"] ? result["key1"]["key2"]["key3"]["key4"].to_i : 123 

¿Hay alguna manera de simplificar esto?

De vez en cuando defino un método como este.

 def value_at_deep_key hash, path, default=nil path.inject(hash) {|current,segment| current && current[segment]} || default end 

Esto usa inyectar para agarrar cada nivel del hash a su vez. Usted usaría así

 value_at_deep_key(result, %w(key1 key2 key3 key4), 123) 

Personalmente, no me gusta el uso de rescate para este tipo de cosas, ya que puede ocultar errores.

 count = result["key1"]["key2"]["key3"]["key4"].to_i rescue 123 

Si quieres hacer un método privado para una mejor legibilidad, podrías hacerlo.

 def count(result) result["key1"]["key2"]["key3"]["key4"].to_i rescue NoMethodError 123 end 

Agrego el NoMethodError para limitar los errores que el rescate puede tragar. A pesar de los argumentos sobre el uso de excepciones para el control de flujo, prefiero esto para facilitar la lectura. En una función pequeña o en un revestimiento, técnicamente ni siquiera cambia el flujo, ya que todo permanece contenido en una ubicación.

Si se usa dentro de un circuito cerrado con millones de registros, es posible que desee comparar con otras soluciones usando un generador de perfiles, pero debe hacer esa llamada en función del uso real. Si esto se usa en un poco de código que puede ejecutarse 5 veces al día, quédese con lo que es más fácil de leer y mantener.

Lo escribiría así y lo pondría en un módulo para incluirlo cuando sea necesario.

Código

 def value_at_deep_key(hash, path) path.each_with_index.reduce(hash) do |current, (segment, i) | case c = current[segment] when Hash then c else (i==path.size-1) ? (current.key?(segment) ? c : :NO_MATCH) : {} end end end 

Ejemplos

 value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b, :c]) #=> "cat" value_at_deep_key({a: {b: {c: false}}}, [:a, :b, :c]) #=> false value_at_deep_key({a: {b: {c: nil}}}, [:a, :b, :c]) #=> nil value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b]) #=> {:c=>"cat"} value_at_deep_key({a: {b: {c: "cat"}}}, [:a]) #=> {:b=>{:c=>"cat"}} value_at_deep_key({z: {b: {c: "cat"}}}, []) #=> {:z=>{:b=>{:c=>"cat"}}} value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c, :d]) #=> :NO_MATCH 

Entonces se podría escribir:

 val = value_at_deep_key(hash, path) (val = 123) if (val == :NO_MATCH) 

Si el valor de la última clave no puede ser nil ,

 else (i==path.size-1) ? (current.key?(segment) ? c : :NO_MATCH) : {} 

podría ser reemplazado por:

 else (i==path.size-1) ? c : {} 

en cuyo caso nil se devolvería cuando no hay coincidencia.

La respuesta aceptada no funciona muy bien:

Aquí está el código:

 def value_at_deep_key(hash, path, default=nil) path.inject(hash) {|current,segment| current && current[segment]} || default end 

Aquí hay algunos resultados:

1) ——————–

 h = { 'key1' => {'key2' => {'key3' => {'key4' => 3}}} } p value_at_deep_key(h, %w[key1 key2 key3 key4], 123) --output:-- 3 

2) ——————–

 h = { 'key1' => 1, 'key2' => 2, 'key3' => 3, 'key4' => 4, } p value_at_deep_key(h, %w[key1 key2 key3 key4], 123) --output:-- 1.rb:16:in `[]': no implicit conversion of String into Integer (TypeError) from 1.rb:16:in `block in value_at_deep_key' from 1.rb:16:in `each' from 1.rb:16:in `inject' from 1.rb:16:in `value_at_deep_key' from 1.rb:19:in `
'

3) ———————

 h = { 'key1' => {'key2' => {'key3' => 4}} } p value_at_deep_key(h, %w[key1 key2 key3 key4], 123) --output:-- 1.rb:16:in `[]': no implicit conversion of String into Integer (TypeError) 

La siguiente respuesta parece funcionar mejor:

 def value_at_deep_key(hash, key_sequence, default=nil) return "No keys to lookup!" if key_sequence.empty? value = hash key_sequence.each do |key| case value when Hash value = value[key] else value = nil break end end value.nil? ? default : Integer(value) #A found value of nil produces the default, which is #also the case when one of the keys doesn't exist in the Hash. #Because to_i() will silently convert a found string with no leading numbers to 0, #use Integer() instead, which will throw a descriptive error when trying to convert any String(or Hash or Array) to an int. end --output:-- p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> `Integer': invalid value for Integer(): "cat" (ArgumentError) p value_at_deep_key({a: {b: {c: false}}}, [:a, :b, :c], 123) #=> `Integer': can't convert false into Integer (TypeError) p value_at_deep_key({a: {b: {c: nil}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b], 123) #=> `Integer': can't convert Hash into Integer (TypeError p value_at_deep_key({a: {b: {c: "cat"}}}, [:a], 123) #=> `Integer': can't convert Hash into Integer (TypeError) p value_at_deep_key({z: {b: {c: "cat"}}}, [], 123) #=> "No keys to lookup!" p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c, :d], 123) #=> 123 p value_at_deep_key( {'key1' => {'key2' => {'key3' => {'key4' => "4"}}}}, %w[key1 key2 key3 key4], default=123, ) #=> 4 p value_at_deep_key( { 'key1' => {'key2' => {'key3' => "4"}}}, %w[key1 key2 key3 key4], default=123, ) #=> 123 p value_at_deep_key( { 'key1' => "1", 'key2' => "2", 'key3' => "3", 'key4' => "4", }, %w[key1 key2 key3 key4], default=123, ) #=> 123 p value_at_deep_key( { 'key1' => {'key2' => {'key3' => {'key4' => nil}}}}, %w[key1 key2 key3 key4], default=123, ) #=> 123 p value_at_deep_key( {'key1' => {'key2' => {'key3' => {'key4' => 'hello'}}}}, %w[key1 key2 key3 key4], default=123, ) #=> `Integer': invalid value for Integer(): "hello" (ArgumentError) 

Pero tal vez la siguiente respuesta te quede mejor:

Si debe tener:

  1. Una cadena encontrada que parece un número, convertida en un int, o
  2. El valor por defecto

… en otras palabras, no hay errores, puedes hacer esto:

 def value_at_deep_key(hash, key_sequence, default=nil) value = hash key_sequence.each do |key| case value when Hash value = value[key] else value = hash.object_id #Some unique value to signal that the Hash lookup failed. break end end begin value == hash.object_id ? default : Integer(value) rescue TypeError, ArgumentError #If the Hash lookup succeeded, but the value is: nil, true/false, a String that is not all numbers, Array, Hash, an object that neither responds to to_int() nor to_i() default end end p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {b: {c: false}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {b: {c: nil}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b], 123) #=> 123 p value_at_deep_key({a: {b: {c: "cat"}}}, [:a], 123) #=> 123 p value_at_deep_key({z: {b: {c: "cat"}}}, [], 123) #=> 123 p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123 p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c, :d], 123) #=> 123 p value_at_deep_key( {'key1' => {'key2' => {'key3' => {'key4' => "4"}}}}, %w[key1 key2 key3 key4], default=123, ) #=> 4 p value_at_deep_key( { 'key1' => {'key2' => {'key3' => "4"}}}, %w[key1 key2 key3 key4], default=123, ) #=> 123 p value_at_deep_key( { 'key1' => "1", 'key2' => "2", 'key3' => "3", 'key4' => "4", }, %w[key1 key2 key3 key4], default=123, ) #=> 123 p value_at_deep_key( { 'key1' => {'key2' => {'key3' => {'key4' => nil}}}}, %w[key1 key2 key3 key4], default=123, ) #=> 123 p value_at_deep_key( {'key1' => {'key2' => {'key3' => {'key4' => [1, 2, 3] }}}}, %w[key1 key2 key3 key4], default=123, ) #=> 123