如何转换Ruby哈希以使其所有键都是符号?

时间:2011-12-04 23:56:14

标签: ruby hash

我有一个Ruby哈希,看起来像:

{ "id" => "123", "name" => "test" }

我想将其转换为:

{ :id => "123", :name => "test" }

15 个答案:

答案 0 :(得分:66)

hash = {"apple" => "banana", "coconut" => "domino"}
Hash[hash.map{ |k, v| [k.to_sym, v] }]
#=> {:apple=>"banana", :coconut=>"domino"}

<强>更新

@mu太短了:没有看到“递归”这个词,但如果你坚持(同时保护不存在to_sym,只想在Ruby 1.8 1.to_sym == nil中提醒一下,所以玩一些关键类型可能会产生误导):

hash = {"a" => {"b" => "c"}, "d" => "e", Object.new => "g"}

s2s = 
  lambda do |h| 
    Hash === h ? 
      Hash[
        h.map do |k, v| 
          [k.respond_to?(:to_sym) ? k.to_sym : k, s2s[v]] 
        end 
      ] : h 
  end

s2s[hash] #=> {:d=>"e", #<Object:0x100396ee8>=>"g", :a=>{:b=>"c"}}

答案 1 :(得分:54)

如果您恰好在Rails中,那么您将拥有symbolize_keys

  

返回一个新哈希,所有密钥都转换为符号,只要它们响应to_sym

symbolize_keys!执行相同操作但就地操作。所以,如果你在Rails中,你可以:

hash.symbolize_keys!

如果你想递归地象征内部哈希,那么我认为你必须自己做,但有这样的事情:

def symbolize_keys_deep!(h)
  h.keys.each do |k|
    ks    = k.to_sym
    h[ks] = h.delete k
    symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
  end
end

您可能希望使用kind_of? Hash来符合您的具体情况;使用respond_to? :keys可能更有意义。如果您想要允许不理解to_sym的密钥,那么:

def symbolize_keys_deep!(h)
  h.keys.each do |k|
    ks    = k.respond_to?(:to_sym) ? k.to_sym : k
    h[ks] = h.delete k # Preserve order even when k == ks
    symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
  end
end

请注意,h[ks] = h.delete kk == ks时不会更改哈希的内容,但是当您使用Ruby 1.9+时它会保留顺序。您也可以使用Rails在[(key.to_sym rescue key) || key]中使用的symbolize_keys!方法,但我认为这是滥用异常处理系统。

第二个symbolize_keys_deep!改变了这个:

{ 'a' => 'b', 'c' => { 'd' => { 'e' => 'f' }, 'g' => 'h' }, ['i'] => 'j' }

进入这个:

{ :a => 'b', :c => { :d => { :e => 'f' }, :g => 'h' }, ['i'] => 'j' }

你可以将symbolize_keys_deep!版本的{{1}}修补到Hash中,如果你真的想要,但除非我有充分的理由这样做,否则我总是远离猴子补丁。

答案 2 :(得分:40)

如果您使用的是Rails&gt; = 4,则可以使用:

hash.deep_symbolize_keys
hash.deep_symbolize_keys!

hash.deep_stringify_keys
hash.deep_stringify_keys!

请参阅http://apidock.com/rails/v4.2.1/Hash/deep_symbolize_keys

答案 3 :(得分:18)

如果您正在解析JSON,可以从json docs添加选项,以便在解析时对符号进行符号化处理:

hash = JSON.parse(json_data, symbolize_names: true)

答案 4 :(得分:10)

Victor Moroz为简单的递归情况提供了一个可爱的答案,但它不会处理嵌套在嵌套数组中的哈希:

hash = { "a" => [{ "b" => "c" }] }
s2s[hash] #=> {:a=>[{"b"=>"c"}]}

如果你需要在哈希中支持数组中的哈希,你会想要更像这样的东西:

def recursive_symbolize_keys(h)
  case h
  when Hash
    Hash[
      h.map do |k, v|
        [ k.respond_to?(:to_sym) ? k.to_sym : k, recursive_symbolize_keys(v) ]
      end
    ]
  when Enumerable
    h.map { |v| recursive_symbolize_keys(v) }
  else
    h
  end
end

答案 5 :(得分:5)

试试这个:

hash = {"apple" => "banana", "coconut" => "domino"}
 # => {"apple"=>"banana", "coconut"=>"domino"} 

hash.tap do |h|
  h.keys.each { |k| h[k.to_sym] = h.delete(k) }
end
 # => {:apple=>"banana", :coconut=>"domino"} 

这会遍历密钥,对于每个密钥,它会删除字符串化密钥并将其值赋给符号化密钥。

答案 6 :(得分:4)

如果您正在使用Rails(或仅使用activesupport):

{ "id" => "123", "name" => "test" }.symbolize_keys

答案 7 :(得分:4)

Ruby单线程比选择的答案更快

hash = {"apple" => "banana", "coconut" => "domino"}
#=> {"apple"=>"banana", "coconut"=>"domino"}

hash.inject({}){|h,(k,v)| h[k.intern] = v; h}
#=> {:apple=>"banana", :coconut=>"domino"}

基准测试结果

n = 100000

Benchmark.bm do |bm|
  bm.report { n.times { hash.inject({}){|h,(k,v)| h[k.intern] = v; h} } }
  bm.report { n.times { Hash[hash.map{ |k, v| [k.to_sym, v] }] } }
end

# =>       user     system      total        real
# =>   0.100000   0.000000   0.100000 (  0.107940)
# =>   0.120000   0.010000   0.130000 (  0.137966)

答案 8 :(得分:4)

从Ruby 2.5开始,您可以使用transform_key方法:https://docs.ruby-lang.org/en/trunk/Hash.html#method-i-transform_keys

所以在你的情况下会是:

h = { "id" => "123", "name" => "test" }
h.transform_keys!(&:to_sym)      #=> {:id=>"123", :name=>"test"}

注意:Ruby on Rails也提供相同的方法。

答案 9 :(得分:2)

我偏爱:

irb
ruby-1.9.2-p290 :001 > hash = {"apple" => "banana", "coconut" => "domino"}
{
      "apple" => "banana",
    "coconut" => "domino"
}
ruby-1.9.2-p290 :002 > hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
{
      :apple => "banana",
    :coconut => "domino"
}

这是有效的,因为我们正在迭代哈希并动态构建一个新哈希。它不是递归的,但你可以通过查看其他一些答案来解决这个问题。

hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }

答案 10 :(得分:2)

您还可以扩展核心Hash ruby​​类放置/lib/hash.rb文件:

class Hash
  def symbolize_keys_deep!
    new_hash = {}
    keys.each do |k|
      ks    = k.respond_to?(:to_sym) ? k.to_sym : k
      if values_at(k).first.kind_of? Hash or values_at(k).first.kind_of? Array
        new_hash[ks] = values_at(k).first.send(:symbolize_keys_deep!)
      else
        new_hash[ks] = values_at(k).first
      end
    end

    new_hash
  end
end

如果要确保包含在父哈希中的数组中的任何哈希的键是符号化的,则还需要扩展数组类,使用该代码创建“array.rb”文件:

class Array
  def symbolize_keys_deep!
    new_ar = []
    self.each do |value|
      new_value = value
      if value.is_a? Hash or value.is_a? Array
        new_value = value.symbolize_keys_deep!
      end
      new_ar << new_value
    end
    new_ar
  end
end

这允许调用“symbolize_keys_deep!”在任何这样的哈希变量上:

myhash.symbolize_keys_deep!

答案 11 :(得分:1)

def symbolize_keys(hash)
   new={}
   hash.map do |key,value|
        if value.is_a?(Hash)
          value = symbolize_keys(value) 
        end
        new[key.to_sym]=value
   end        
   return new

end  
puts symbolize_keys("c"=>{"a"=>2,"k"=>{"e"=>9}})
#{:c=>{:a=>2, :k=>{:e=>9}}}

答案 12 :(得分:1)

这是我的两分钱,

我的symbolize_keys_deep!版本使用原始的symbolize_keys!由rails提供,只是对Symbolize子哈希进行简单的递归调用。

  def symbolize_keys_deep!(h)
    h.symbolize_keys!
    h.each do |k, v|
      symbolize_keys_deep!(v) if v.is_a? Hash
    end
  end

答案 13 :(得分:1)

Facets' Hash#rekey也值得一提。

样品:

require 'facets/hash/rekey'
{ "id" => "123", "name" => "test" }.deep_rekey
=> {:id=>"123", :name=>"test"}

还有一个递归版本:

require 'facets/hash/deep_rekey'
{ "id" => "123", "name" => {"first" => "John", "last" => "Doe" } }.deep_rekey
=> {:id=>"123", :name=>{:first=>"John", :last=>"Doe"}}

答案 14 :(得分:0)

这是一个小的递归函数来对键进行深度符号化:

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