我有一个Ruby哈希,看起来像:
{ "id" => "123", "name" => "test" }
我想将其转换为:
{ :id => "123", :name => "test" }
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"}}
我偏爱:
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 }
您还可以扩展核心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!
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}}}
这是我的两分钱,
我的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
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"}}
这里有一个小的递归函数来对键进行深度符号化:
def symbolize_keys(hash)
Hash[hash.map{|k,v| v.is_a?(Hash) ? [k.to_sym, symbolize_keys(v)] : [k.to_sym, v] }]
end
如果您碰巧在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 k
在k == ks
时不会更改Hash的内容,但是当您使用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!
的任何一个版本修补到哈希,但除非我有很好的理由,否则我通常会远离猴子修补。
如果您使用Rails> = 4,您可以使用:
hash.deep_symbolize_keys
hash.deep_symbolize_keys!
要么
hash.deep_stringify_keys
hash.deep_stringify_keys!
为了防止您解析JSON,您可以在json docs中添加选项以在解析时对符号进行符号化:
hash = JSON.parse(json_data, symbolize_names: true)
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
试试这个:
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"}
这会遍历密钥,对于每个密钥,它会删除字符串化密钥并将其值分配给符号化密钥。
如果您正在使用Rails(或仅使用activesupport):
{ "id" => "123", "name" => "test" }.symbolize_keys
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)
从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也提供相同的方法。