ruby on rails - Cómo elegantemente symbolize_keys para un hash ''anidado''
ruby-on-rails dry (6)
En los rieles puede crear la clase HashWithIndifferentAccess. Cree una instancia de esta clase pasando su hash a su constructor y luego acceda a él con claves que son símbolos o cadenas (como params de Acciones del Controlador):
hash = {''a'' => {''b'' => [{c: 3}]}}
hash = hash.with_indifferent_access
# equal to:
# hash = ActiveSupport::HashWithIndifferentAccess.new(hash)
hash[:a][:b][0][:c]
=> 3
Considera el siguiente código:
hash1 = {"one" => 1, "two" => 2, "three" => 3}
hash2 = hash1.reduce({}){ |h, (k,v)| h.merge(k => hash1) }
hash3 = hash2.reduce({}){ |h, (k,v)| h.merge(k => hash2) }
hash4 = hash3.reduce({}){ |h, (k,v)| h.merge(k => hash3) }
hash4 es un hash "anidado", es decir, un hash con claves de cadena y valores de hash "anidados" similares.
El método ''symbolize_keys'' para Hash in Rails nos permite convertir fácilmente las claves de cadena en símbolos. Pero estoy buscando una forma elegante de convertir todas las claves (claves principales más claves de todos los hash dentro de hash4) a símbolos.
El punto es salvarme de mi (fea) solución:
class Hash
def symbolize_keys_and_hash_values
symbolize_keys.reduce({}) do |h, (k,v)|
new_val = v.is_a?(Hash) ? v.symbolize_keys_and_hash_values : v
h.merge({k => new_val})
end
end
end
hash4.symbolize_keys_and_hash_values #=> desired result
FYI: La configuración es Rails 3.2.17 y Ruby 2.1.1
Actualizar:
La respuesta es hash4.deep_symbolize_keys
para Rails <= 5.0
La respuesta es JSON.parse(JSON[hash4], symbolize_names: true)
para Rails> 5
Hay un api.rubyonrails.org/classes/…
hash4.deep_symbolize_keys!
Podrías usar:
- Hash#to_s para convertir el hash en una cadena;
- String#gsub con una expresión regular para convertir las claves de cadenas en representaciones de símbolos; y entonces
- Kernel#eval para convertir la cadena de nuevo en un hash.
Esta es una solución fácil para su problema, pero solo debería considerar usarla si puede confiar en que eval
no va a producir algo desagradable. Si tienes control sobre el contenido del hash que se está convirtiendo, eso no debería ser un problema.
Este enfoque podría usarse para otros tipos de objetos anidados, como los que contienen tanto matrices como hash.
Código
def symbolize_hash(h)
eval(h.to_s.gsub(//"(/w+)/"(?==>)/, '':/1''))
end
Ejemplos
symbolize_hash(hash4)
#=> {:one=>{:one=> {:one=> {:one=>1, :two=>2, :three=>3},
# :two=> {:one=>1, :two=>2, :three=>3},
# :three=>{:one=>1, :two=>2, :three=>3}},
# :two=> {:one=> {:one=>1, :two=>2, :three=>3},
# :two=> {:one=>1, :two=>2, :three=>3},
# :three=>{:one=>1, :two=>2, :three=>3}},
# :three=>{:one=> {:one=>1, :two=>2, :three=>3},
# :two=> {:one=>1, :two=>2, :three=>3},
# :three=>{:one=>1, :two=>2, :three=>3}}},
# :two=>{:one=> {:one=> {:one=>1, :two=>2, :three=>3},
# ...
# :three=>{:one=>{:one=> {:one=>1, :two=>2, :three=>3},
# ...
# :three=>{:one=>1, :two=>2, :three=>3}}}}
symbolize_hash({''a''=>1, ''b''=>[{''c''=>{''d''=>''d''}}, {e:''f''}]})
#=> {:a=>1, :b=>[{:c=>{:d=>"d"}}, {:e=>"f"}]}
Explicación
(?==>)
en el regex es un look-up posivo de ancho cero. ?=
significa un avance positivo; =>
es la cadena que debe seguir inmediatamente a la coincidencia con /"(/w+)/"
. /1
en '':/1''
(o podría haber escrito "://1"
) es una cadena que comienza con dos puntos seguidos de una retro referencia al contenido del grupo de captura # 1, la coincidencia de claves /w+
(sin las comillas) )
Puedo sugerir algo como esto:
class Object
def deep_symbolize_keys
self
end
end
class Hash
def deep_symbolize_keys
symbolize_keys.tap { |h| h.each { |k, v| h[k] = v.deep_symbolize_keys } }
end
end
{''a''=>1, ''b''=>{''c''=>{''d''=>''d''}, e:''f''}, ''g''=>1.0, ''h''=>nil}.deep_symbolize_keys
# => {:a=>1, :b=>{:c=>{:d=>"d"}, :e=>"f"}, :g=>1.0, :h=>nil}
También puede extenderlo fácilmente para admitir Arrays
:
class Array
def deep_symbolize_keys
map(&:deep_symbolize_keys)
end
end
{''a''=>1, ''b''=>[{''c''=>{''d''=>''d''}}, {e:''f''}]}.deep_symbolize_keys
# => {:a=>1, :b=>[{:c=>{:d=>"d"}}, {:e=>"f"}]}
Puedo sugerir:
JSON.parse (hash_value.to_json)
Ya no puedes usar este método para params o cualquier otra instancia de ActionController::Parameters
, porque el método deep_symbolize_keys
está en desuso en Rails 5.0+ por razones de seguridad y se eliminará en Rails 5.1+ ya que ActionController::Parameters
ya no hereda de Hash
Entonces este enfoque de @Uri Agassi parece ser el universal .
JSON.parse(JSON[h], symbolize_names: true)
Sin embargo, el objeto Rails Hash todavía lo tiene.
Entonces las opciones son:
si no usas Rails o simplemente no te importa:
JSON.parse(JSON[h], symbolize_names: true)
con Rails y ActionController :: Parámetros:
params.to_unsafe_h.deep_symbolize_keys
con Rails y Hash simple
h.deep_symbolize_keys