У меня есть хэш Ruby, который выглядит так:
{ "id" => "123", "name" => "test" }
Я хотел бы преобразовать его в:
{ :id => "123", :name => "test" }
У меня есть хэш 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"}
Update:
@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"}}
Если вы оказались в 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
kind_of? Hash
чтобы соответствовать вашим конкретным обстоятельствам; используя respond_to? :keys
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
но сохраняет порядок, когда вы используете Ruby 1. 9+. Вы также можете использовать [(key.to_sym rescue key) || key]
[(key.to_sym rescue key) || key]
подход, который Rails использует в их 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!
см. http://apidock.com/rails/v4.2.1/Hash/deep_symbolize_keys
На всякий случай, когда вы разбираете JSON, из json docs вы можете добавить опцию для обозначения ключей при разборе:
hash = JSON.parse(json_data, symbolize_names: true)
Виктор Мороз предоставил прекрасный ответ для простого рекурсивного случая, но он не будет обрабатывать хэши, которые вложены в вложенные массивы:
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
Рубиновый однострочный, который быстрее, чем выбранный ответ
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.
Я неравнодушен к:
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, поместив файл /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! предоставляемый рельсами, и просто делает простой рекурсивный вызов Symbolize sub hashes.
def symbolize_keys_deep!(h)
h.symbolize_keys!
h.each do |k, v|
symbolize_keys_deep!(v) if v.is_a? Hash
end
end
Графит хэша # 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