ruby-on-rails ruby hash dry

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



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