La mejor manera de convertir cadenas en símbolos en hash

250

¿Cuál es la forma (más rápida / más limpia / directa) de convertir todas las claves en un hash de cadenas a símbolos en Ruby?

Esto sería útil al analizar YAML.

my_hash = YAML.load_file('yml')

Me gustaría poder usar:

my_hash[:key] 

Más bien que:

my_hash['key']
Bryan M.
fuente
dup ?
Ian Vaughan
80
hash.symbolize_keysy hash.deep_symbolize_keyshaz el trabajo si estás usando Rails.
Zaz
Josh, si hubieras puesto tu comentario en una respuesta, te habría votado. require 'rails'; hash.deep_symbolize_keys funciona bastante bien en irb o en palanca. : D
Douglas G. Allen

Respuestas:

239

En Ruby> = 2.5 ( docs ) puede usar:

my_hash.transform_keys(&:to_sym)

¿Usa una versión anterior de Ruby? Aquí hay una línea que copiará el hash en uno nuevo con las teclas simbolizadas:

my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}

Con Rails puedes usar:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 
Sarah Mei
fuente
55
Ah, perdón por no estar claro: inyectar no modifica a la persona que llama. Debe hacer my_hash = my_hash.inject ({}) {| memo, (k, v) | memo [k.to_sym] = v; memo}
Sarah Mei
44
Eso es exactamente lo que estaba buscando. Lo modifiqué un poco y agregué algunas líneas para incluso crear símbolos en hashes anidados. Eche un vistazo aquí, si está interesado: any-where.de/blog/ruby-hash-convert-string-keys-to-symbols
Matt
37
En Ruby 1.9 puede usar each_with_objectasí:my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd
8
esto no maneja los hashes recursivos ... Encuentra para un único pero no para SECO.
baash05
8
@BryanM. Llegué a esta discusión muy tarde :-) pero también puede usar el .tapmétodo para eliminar la necesidad de aprobar memoal final. He creado una versión limpia de todas las soluciones (también las recursivas) gist.github.com/Integralist/9503099
Integralist
307

Aquí hay un método mejor, si está utilizando Rails:

params simbolizar_claves

El fin.

Si no lo está, simplemente copie su código (también está en el enlace):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
Sai
fuente
66
to_options es un alias para sybolize_keys .
ma11hew28
45
No simbolizará hashes anidados.
oma
3
Cambié el enlace a symbolize_keysla nueva y funcional URL (Rails 3). Originalmente solo arreglé la URL para to_options, pero no hay documentación en ese enlace. symbolize_keysen realidad tiene una descripción, así que la usé en su lugar.
Craig Walker
23
deep_symbolize_keys! . Funciona en rails 2+
mastaBlasta
14
Para aquellos curiosos de cómo hacer lo contrario, hash.stringify_keysfunciona.
Nick
112

Para el caso específico de YAML en Ruby, si las teclas comienzan con ' :', serán internados automáticamente como símbolos.

requiere 'yaml'
requiere 'pp'
yaml_str = "
conexiones:
  - host: host1.example.com
    puerto: 10000
  - host: host2.example.com
    puerto: 20000
"
yaml_sym = "
: conexiones:
  -: host: host1.example.com
    : puerto: 10000
  -: host: host2.example.com
    : puerto: 20000
"
pp yaml_str = YAML.load (yaml_str)
pone yaml_str.keys.first.class
pp yaml_sym = YAML.load (yaml_sym)
pone yaml_sym.keys.first.class

Salida:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{"conexiones" =>
  [{"puerto" => 10000, "host" => "host1.example.com"},
   {"port" => 20000, "host" => "host2.example.com"}]}
Cuerda
{: conexiones =>
  [{: port => 10000,: host => "host1.example.com"},
   {: puerto => 20000,: host => "host2.example.com"}]}
Símbolo
jrgm
fuente
15
¡Dulce! ¿Hay alguna manera de establecer YAML#load_filepor defecto todas las teclas de símbolos en lugar de cadenas sin tener que comenzar cada tecla con dos puntos?
ma11hew28
63

Aún más conciso:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
Michael Barton
fuente
66
Esta parece ser la opción obvia.
Casey Watson
12
No simboliza hashes anidados.
rgtk
15
Una versión más legible:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Dennis
60

si usa Rails, es mucho más simple: puede usar un acceso HashWithIndifferentAccess y acceder a las teclas como String y Symbols:

my_hash.with_indifferent_access 

ver también:

http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html


O puede usar la increíble Gema "Facetas de Ruby", que contiene muchas extensiones para las clases Ruby Core y Standard Library.

  require 'facets'
  > {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
    =>  {:some=>"thing", :foo=>"bar}

ver también: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash

Tilo
fuente
2
En realidad eso hace lo contrario. Se convierte de símbolo a una cadena. Para convertir a un símbolo, use my_hash.symbolize_keys
Espen el
#symbolize_keys solo funciona en Rails, no en Ruby / irb simple. También tenga en cuenta que #symbolize_keys no funciona en hashes profundamente anidados.
Tilo
54

Ya Ruby 2.5.0que puedes usar Hash#transform_keyso Hash#transform_keys!.

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}
Sagar Pandya
fuente
También funciona con transform_values apidock.com/rails/Hash/transform_values . Eso es realmente bueno porque no parece haber un equivalente para modificar valores como los hay con stringify_keyso symbolize_keys.
Mike Vallano
¿Hay alguna manera de simbolizar las teclas?
Abhay Kumar
Esta debería ser la solución elegida después de 2018.
Ivan Wang
26

Aquí hay una manera de simbolizar profundamente un objeto

def symbolize(obj)
    return obj.inject({}){|memo,(k,v)| memo[k.to_sym] =  symbolize(v); memo} if obj.is_a? Hash
    return obj.inject([]){|memo,v    | memo           << symbolize(v); memo} if obj.is_a? Array
    return obj
end
igorsales
fuente
bonito, iré con este incluso si lo cambiara de nombre deep_symbolize :)
PierrOz
20

Realmente me gusta la gema Mash .

puedes hacer mash['key'], o mash[:key], omash.key

ykaganovich
fuente
2
¡Esa es una joya genial! Hace que trabajar con hashes sea muy cómodo. ¡Gracias por esto!
asaaki
Muy bellamente simple. El nuevo desarrollo de este proyecto continúa en Hashie ( github.com/intridea/hashie ) pero sigue funcionando de la misma manera: github.com/intridea/hashie#keyconversion
jpalmieri
19

Si está utilizando json y desea utilizarlo como hash, en el núcleo de Ruby puede hacerlo:

json_obj = JSON.parse(json_str, symbolize_names: true)

symbolize_names : si se establece en true, devuelve símbolos para los nombres (claves) en un objeto JSON. De lo contrario, se devuelven las cadenas. Las cadenas son las predeterminadas.

Doc: Json # parse symbolize_names

marca
fuente
symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)es una forma bastante SECA (pero ineficiente) de obtener rápidamente un hash con claves anidadas como símbolos si proviene de un archivo YAML.
Cameron Gagnon
12

params.symbolize_keysTambién funcionará. Este método convierte las claves hash en símbolos y devuelve un nuevo hash.

Jae Cho
fuente
26
Ese método no es el núcleo de Ruby. Es un método de Rails.
Ricardo Acras
10

Una modificación a la respuesta @igorsales

class Object
  def deep_symbolize_keys
    return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
    return self.inject([]){|memo,v    | memo           << v.deep_symbolize_keys; memo} if self.is_a? Array
    return self
  end
end
Tony
fuente
Sería útil si incluyeras por qué modificabas el objeto para que las personas escanearan las respuestas.
Dbz
9

En Rails puedes usar:

{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!

Convierte a:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
Jay Jay
fuente
3
deep_symbolize_keysse agrega en la extensión Hash de Rails , pero no es parte del núcleo de Ruby.
Ryan Crispin Heneise
7

Este es mi único forro para hashes anidados

def symbolize_keys(hash)
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end
Nick Dobson
fuente
FYI, solo funciona para Rails. En cuyo caso, HashWithIndifferentAccess puede ser una mejor alternativa.
Adam Grant
6

En caso de que la razón por la que tiene que hacer esto es porque los datos originalmente vinieron de JSON, puede omitir cualquiera de este análisis, mediante el simple en el:symbolize_names opción al ingerir JSON.

No se requieren rieles y funciona con Ruby> 1.9

JSON.parse(my_json, :symbolize_names => true)
Adam Grant
fuente
4

Podrías ser flojo y envolverlo en lambda:

my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }

my_lamb[:a] == my_hash['a'] #=> true

Pero esto solo funcionaría para leer desde el hash, no para escribir.

Para hacer eso, podrías usar Hash#merge

my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))

El bloque init convertirá las claves una vez a pedido, aunque si actualiza el valor para la versión de cadena de la clave después de acceder a la versión del símbolo, la versión del símbolo no se actualizará.

irb> x = { 'a' => 1, 'b' => 2 }
#=> {"a"=>1, "b"=>2}
irb> y = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(x)
#=> {"a"=>1, "b"=>2}
irb> y[:a]  # the key :a doesn't exist for y, so the init block is called
#=> 1
irb> y
#=> {"a"=>1, :a=>1, "b"=>2}
irb> y[:a]  # the key :a now exists for y, so the init block is isn't called
#=> 1
irb> y['a'] = 3
#=> 3
irb> y
#=> {"a"=>3, :a=>1, "b"=>2}

También podría hacer que el bloque de inicio no actualice el hash, lo que lo protegería de ese tipo de error, pero aún sería vulnerable a lo contrario: actualizar la versión del símbolo no actualizaría la versión de la cadena:

irb> q = { 'c' => 4, 'd' => 5 }
#=> {"c"=>4, "d"=>5}
irb> r = Hash.new { |h,k| h[k.to_s] }.merge(q)
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called
#=> 4
irb> r
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called again, since this key still isn't in r
#=> 4
irb> r[:c] = 7
#=> 7
irb> r
#=> {:c=>7, "c"=>4, "d"=>5}

Entonces, lo que hay que tener cuidado con esto es cambiar entre las dos formas clave. Quédate con uno.

rampion
fuente
3

¿Algo como el siguiente trabajo?

new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }

Copiará el hash, pero eso no te importará la mayor parte del tiempo. Probablemente haya una manera de hacerlo sin copiar todos los datos.

ChrisInEdmonton
fuente
3

un fwiw de una línea más corto:

my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }
sensadrome
fuente
2

Qué tal esto:

my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))

# my_hash['key'] => "val"
# my_hash[:key]  => "val"
Fredrik Boström
fuente
2

Esto es para personas que usan mrubyy no tienen ningún symbolize_keysmétodo definido:

class Hash
  def symbolize_keys!
    self.keys.each do |k|
      if self[k].is_a? Hash
        self[k].symbolize_keys!
      end
      if k.is_a? String
        raise RuntimeError, "Symbolizing key '#{k}' means overwrite some data (key :#{k} exists)" if self[k.to_sym]
        self[k.to_sym] = self[k]
        self.delete(k)
      end
    end
    return self
  end
end

El método:

  • simboliza solo las teclas que son String
  • si simbolizar una cadena significa perder información (sobrescribir parte del hash) RuntimeError
  • simboliza también hashes recursivamente contenidos
  • devolver el hash simbolizado
  • funciona en su lugar!
Matteo Ragni
fuente
1
Hay un error tipográfico en tu método, olvidaste el !in symbolize_keys. De lo contrario funciona bien.
Ka Mok
1

La matriz que queremos cambiar.

cadenas = ["HTML", "CSS", "JavaScript", "Python", "Ruby"]

Haga una nueva variable como una matriz vacía para que podamos "empujar" los símbolos.

símbolos = []

Aquí es donde definimos un método con un bloque.

strings.each {| x | símbolos.push (x.intern)}

Fin del código.

Por lo tanto, esta es probablemente la forma más sencilla de convertir cadenas en símbolos en su (s) matriz (s) en Ruby. Haga una matriz de cadenas, luego haga una nueva variable y establezca la variable en una matriz vacía. Luego seleccione cada elemento en la primera matriz que creó con el método ".each". Luego use un código de bloque para ".push" todos los elementos en su nueva matriz y use ".intern o .to_sym" para convertir todos los elementos en símbolos.

Los símbolos son más rápidos porque ahorran más memoria dentro de su código y solo puede usarlos una vez. Los símbolos se usan más comúnmente para claves en hash, lo cual es genial. No soy el mejor programador de ruby, pero esta forma de código me ayudó mucho. ¡Si alguien conoce una mejor manera, por favor comparte y puedes usar este método para el hash también!

rubyguest123
fuente
2
La pregunta era sobre hashes, no matrices.
Richard_G
1

Si desea una solución de vainilla rubí y como yo no tengo acceso ActiveSupportaquí, es una solución de simbolización profunda (muy similar a las anteriores)

    def deep_convert(element)
      return element.collect { |e| deep_convert(e) } if element.is_a?(Array)
      return element.inject({}) { |sh,(k,v)| sh[k.to_sym] = deep_convert(v); sh } if element.is_a?(Hash)
      element
    end
Haris Krajina
fuente
1

A partir de Psych 3.0, puede agregar la opción symbolize_names:

Psych.load("---\n foo: bar") # => {"foo"=>"bar"}

Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}

Nota: si tiene una versión de Psych inferior a la 3.0 symbolize_names: , se ignorará en silencio.

Mi Ubuntu 18.04 lo incluye listo para usar con ruby ​​2.5.1p57

Rodrigo Estebanez
fuente
0
ruby-1.9.2-p180 :001 > h = {'aaa' => 1, 'bbb' => 2}
 => {"aaa"=>1, "bbb"=>2} 
ruby-1.9.2-p180 :002 > Hash[h.map{|a| [a.first.to_sym, a.last]}]
 => {:aaa=>1, :bbb=>2}
Vlad Khomich
fuente
Puede ajustarlo aentre paréntesis para descomponer el argumento de bloque para hacerlo aún más conciso. Vea mi respuesta por ejemplo.
Michael Barton
0

Esto no es exactamente una línea, pero convierte todas las teclas de cadena en símbolos, también las anidadas:

def recursive_symbolize_keys(my_hash)
  case my_hash
  when Hash
    Hash[
      my_hash.map do |key, value|
        [ key.respond_to?(:to_sym) ? key.to_sym : key, recursive_symbolize_keys(value) ]
      end
    ]
  when Enumerable
    my_hash.map { |value| recursive_symbolize_keys(value) }
  else
    my_hash
  end
end
ChristofferJoergensen
fuente
0

Me gusta esta frase, cuando no estoy usando Rails, porque entonces no tengo que hacer un segundo hash y mantener dos conjuntos de datos mientras lo estoy procesando:

my_hash = { "a" => 1, "b" => "string", "c" => true }

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key) }

my_hash
=> {:a=>1, :b=>"string", :c=>true}

Hash # delete devuelve el valor de la clave eliminada

nevets138
fuente
0

Facets 'Hash # deep_rekey también es una buena opción, especialmente:

  • si encuentra uso para otro azúcar de facetas en su proyecto,
  • si prefiere la legibilidad del código en lugar de líneas crípticas.

Muestra:

require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey
Sergikon
fuente
0

En ruby, encuentro que esta es la forma más simple y fácil de entender para convertir las teclas de cadena en hashes a símbolos:

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}

Para cada clave en el hash llamamos eliminar en él que lo elimina del hash (también eliminar devuelve el valor asociado con la clave que se eliminó) e inmediatamente establecemos esto igual a la clave simbolizada.


fuente
0

Similar a las soluciones anteriores, pero escrito de manera un poco diferente.

  • Esto permite un hash que está anidado y / o tiene matrices.
  • Obtenga la conversión de claves a una cadena como un bono.
  • El código no muta el hash pasado.

    module HashUtils
      def symbolize_keys(hash)
        transformer_function = ->(key) { key.to_sym }
        transform_keys(hash, transformer_function)
      end
    
      def stringify_keys(hash)
        transformer_function = ->(key) { key.to_s }
        transform_keys(hash, transformer_function)
      end
    
      def transform_keys(obj, transformer_function)
        case obj
        when Array
          obj.map{|value| transform_keys(value, transformer_function)}
        when Hash
          obj.each_with_object({}) do |(key, value), hash|
            hash[transformer_function.call(key)] = transform_keys(value, transformer_function)
          end
        else
          obj
        end
      end
    end
jham
fuente