Melhor maneira de converter seqüências de caracteres em símbolos em hash


250

Qual é a maneira (mais rápida / mais limpa / direta) de converter todas as chaves em um hash de strings para símbolos em Ruby?

Isso seria útil ao analisar o YAML.

my_hash = YAML.load_file('yml')

Eu gostaria de poder usar:

my_hash[:key] 

Ao invés de:

my_hash['key']


80
hash.symbolize_keyse hash.deep_symbolize_keysfaça o trabalho se estiver usando o Rails.
Zaz

Josh, se você colocasse seu comentário em resposta, eu teria votado em você. requer 'rails'; hash.deep_symbolize_keys funciona muito bem no irb ou no pry. : D
Douglas G. Allen

Respostas:


239

Em Ruby> = 2.5 ( docs ), você pode usar:

my_hash.transform_keys(&:to_sym)

Usando a versão mais antiga do Ruby? Aqui está uma linha que copiará o hash para uma nova com as teclas simbolizadas:

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

Com o Rails, você pode usar:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 

5
Ah, desculpe por não estar claro - o injetar não modifica o chamador. Você precisa fazer my_hash = my_hash.inject ({}) {| memorando, (k, v) | memorando [k.to_sym] = v; memo}
Sarah Mei

4
Era exatamente o que eu estava procurando. Eu o modifiquei um pouco e adicionei algumas linhas para criar símbolos em hashes aninhados. Dê uma olhada aqui, se você estiver interessado: any-where.de/blog/ruby-hash-convert-string-keys-to-symbols
Matt

37
No Ruby 1.9, você pode usar each_with_objectassim:my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd

8
isso não lida com hashes recursivos ... Encontre um único, mas não para DRY.
baash05

8
@BryanM. Eu entrei nessa discussão muito tarde :-) mas você também pode usar o .tapmétodo para remover a necessidade de aprovação memono final. Eu criei uma versão limpa de todas as soluções (também recursivas) gist.github.com/Integralist/9503099
Integralist

307

Aqui está um método melhor, se você estiver usando o Rails:

params. symbolize_keys

O fim.

Caso contrário, retire o código (também está no link):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end


45
Não simboliza hashes aninhados.
oma

3
Mudei o link para symbolize_keyso novo & working (Rails 3) URL. Inicialmente, corrigi o URL to_options, mas não há documentação nesse link. symbolize_keysna verdade tem uma descrição, então usei isso.
Craig Walker

23
deep_symbolize_keys! . Funciona nos trilhos 2+
mastaBlasta

14
Para aqueles curiosos como fazer o inverso, hash.stringify_keysfunciona.
Nick

112

Para o caso específico de YAML no Ruby, se as chaves começarem com ' :', elas serão automaticamente internadas como símbolos.

requer 'yaml'
requer 'pp'
yaml_str = "
conexões:
  - host: host1.example.com
    porta: 10000
  - host: host2.example.com
    porta: 20000
"
yaml_sym = "
: conexões:
  -: host: host1.example.com
    : porta: 10000
  -: host: host2.example.com
    : porta: 20000
"
pp yaml_str = YAML.load (yaml_str)
coloca yaml_str.keys.first.class
pp yaml_sym = YAML.load (yaml_sym)
coloca yaml_sym.keys.first.class

Resultado:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{"conexões" =>
  [{"porta" => 10000, "host" => "host1.exemplo.com"},
   {"porta" => 20000, "host" => "host2.exemplo.com"}]}
Corda
{: conexões =>
  [{: port => 10000,: host => "host1.exemplo.com"},
   {: port => 20000,: host => "host2.example.com"}]}
Símbolo

15
Doce! Existe uma maneira de definir YAML#load_filecomo padrão todas as chaves para símbolos em vez de cadeias sem ter que iniciar todas as chaves com dois pontos?
211111 ma11hew28

63

Ainda mais conciso:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]

6
Esta parece ser a escolha óbvia.
Casey Watson

12
Não simboliza hashes aninhados.
Rgtk 17/05

15
Uma versão mais legível:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Dennis

60

se você estiver usando o Rails, é muito mais simples - você pode usar um HashWithIndifferentAccess e acessar as teclas como String e como Symbols:

my_hash.with_indifferent_access 

Veja também:

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


Ou você pode usar a incrível "Facets of Ruby" Gem, que contém muitas extensões para as classes Ruby Core e Standard Library.

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

consulte também: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash


2
Na verdade, isso faz o contrário. Ele converte do símbolo em uma string. Para converter em um símbolo de uso my_hash.symbolize_keys
Espen

#symbolize_keys funciona apenas no Rails - não no Ruby / irb. Observe também que #symbolize_keys não funciona em hashes profundamente aninhados.
Tilo 27/07

54

Desde que Ruby 2.5.0você pode usar Hash#transform_keysou Hash#transform_keys!.

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}

Também funciona com transform_values apidock.com/rails/Hash/transform_values . Isso é muito bom, porque não parece haver um equivalente para modificar valores como existe com stringify_keysor symbolize_keys.
Mike Vallano

há uma maneira de chaves simbolizam profundas
Abhay Kumar

Esta deve ser a solução escolhida depois de 2018.
Ivan Wang


26

Aqui está uma maneira de simbolizar profundamente um 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

nice one, eu vou com este mesmo que eu iria mudar o nome deep_symbolize :)
PierrOz


19

Se você estiver usando json, e quiser usá-lo como um hash, no Core Ruby, poderá fazê-lo:

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

symbolize_names : se definido como true, retorna símbolos para os nomes (chaves) em um objeto JSON. Caso contrário, as strings serão retornadas. Strings são o padrão.

Doc: Json # parse symbolize_names


symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)é uma maneira bastante SECA (mas ineficiente) de obter rapidamente um hash com chaves aninhadas como símbolos se vier de um arquivo YAML.
Cameron Gagnon

12

params.symbolize_keystambém irá funcionar. Esse método transforma chaves de hash em símbolos e retorna um novo hash.


26
Esse método não é o principal Ruby. É um método Rails.
Ricardo Acras

10

Uma modificação na resposta @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

Seria útil se você incluísse o motivo pelo qual estava modificando o objeto para pessoas que vasculham as respostas.
Dbz

9

No Rails você pode usar:

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

Converte para:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}

3
deep_symbolize_keysé adicionado na extensão Hash do Rails , mas não faz parte do núcleo do Ruby.
Ryan Crispin Heneise


7

Este é o meu liner para hashes aninhados

def symbolize_keys(hash)
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end

Para sua informação, funciona apenas para Rails. Nesse caso, o HashWithIndifferentAccess pode ser uma alternativa melhor.
Adam Grant

6

Caso o motivo para você fazer isso seja porque seus dados originalmente vieram do JSON, você pode pular qualquer uma dessas análises apenas passando a :symbolize_namesopção ao ingerir o JSON.

Não é necessário Rails e funciona com Ruby> 1.9

JSON.parse(my_json, :symbolize_names => true)

4

Você pode ser preguiçoso e envolvê-lo em lambda:

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

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

Mas isso funcionaria apenas para leitura do hash - não para escrita.

Para fazer isso, você pode usar Hash#merge

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

O bloco init converterá as chaves uma vez sob demanda, embora, se você atualizar o valor da versão em cadeia da chave após acessar a versão do símbolo, a versão do símbolo não será atualizada.

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}

Você também pode fazer com que o bloco init não atualize o hash, o que o protegerá desse tipo de erro, mas você ainda estará vulnerável ao contrário - a atualização da versão do símbolo não atualizará a versão da string:

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}

Portanto, o que deve ser tomado com cuidado é alternar entre as duas formas principais. Ficar com um.


3

Algo como o seguinte funciona?

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

Ele copiará o hash, mas você não se importará com isso na maioria das vezes. Provavelmente existe uma maneira de fazer isso sem copiar todos os dados.


3

um fwiw de uma linha mais curto:

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

2

Que tal agora:

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

# my_hash['key'] => "val"
# my_hash[:key]  => "val"

2

Isso é para pessoas que usam mrubye não têm nenhum 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

O método:

  • simboliza apenas as teclas que são String
  • se simbolizar uma string significa perder algumas informações (sobrescrever parte do hash) gera um RuntimeError
  • simbolizam também hashes contidos recursivamente
  • retornar o hash simbolizado
  • trabalha no lugar!

1
Há um erro de digitação em seu método, você esqueceu o !no symbolize_keys. Caso contrário, funciona bem.
Ka Mok

1

A matriz que queremos mudar.

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

Crie uma nova variável como uma matriz vazia para que possamos "empurrar" os símbolos.

símbolos = []

Aqui é onde definimos um método com um bloco.

strings.each {| x | symbols.push (x.intern)}

Fim do código.

Portanto, essa é provavelmente a maneira mais direta de converter seqüências de caracteres em símbolos em seu array (s) em Ruby. Faça uma matriz de seqüências de caracteres, crie uma nova variável e defina a variável como uma matriz vazia. Em seguida, selecione cada elemento na primeira matriz que você criou com o método ".each". Em seguida, use um código de bloco para "empurrar" todos os elementos em sua nova matriz e use ".intern ou .to_sym" para converter todos os elementos em símbolos.

Os símbolos são mais rápidos porque economizam mais memória no seu código e você pode usá-los apenas uma vez. Os símbolos são mais comumente usados ​​para chaves em hash, o que é ótimo. Eu não sou o melhor programador de ruby, mas esse tipo de código me ajudou muito. Se alguém souber uma maneira melhor, compartilhe e você também pode usar esse método para o hash!


2
A questão era sobre hashes, não matrizes.
22416 Richard_G

1

Se você gostaria de uma solução de baunilha rubi e como eu não tenho acesso ActiveSupportaqui, é uma solução simbolizada profunda (muito semelhante às 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

1

A partir do Psych 3.0, você pode adicionar a opção symbolize_names:

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

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

Nota: se você tiver uma versão Psych inferior a 3.0, symbolize_names:será ignorada silenciosamente.

Meu Ubuntu 18.04 inclui-o imediatamente com o ruby ​​2.5.1p57


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}

Você pode acolocar colchetes para decompor o argumento do bloco para torná-lo ainda mais conciso. Veja minha resposta, por exemplo.
22812 Michael Barton

0

Isso não é exatamente uma linha, mas transforma todas as chaves de seqüência de caracteres em símbolos, também os aninhados:

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

0

Eu gosto dessa linha única, quando não estou usando o Rails, porque não preciso fazer um segundo hash e reter dois conjuntos de dados enquanto o processo:

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 retorna o valor da chave excluída


0

O hash de facetas # deep_rekey também é uma boa opção, especialmente:

  • se você encontrar uso para outro açúcar das facetas do seu projeto,
  • se você preferir a legibilidade do código em vez de linhas únicas criptográficas.

Amostra:

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

0

Em ruby, acho que essa é a maneira mais simples e fácil de entender de transformar chaves de string em hashes em símbolos:

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

Para cada chave no hash, chamamos delete, que o remove do hash (também delete retorna o valor associado à chave que foi excluída) e imediatamente o definimos como igual à chave simbolizada.


0

Semelhante às soluções anteriores, mas escrito de forma um pouco diferente.

  • Isso permite um hash aninhado e / ou com matrizes.
  • Obtenha a conversão de chaves em uma string como um bônus.
  • O código não altera o hash passado.

    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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.