Как преобразовать хэш Ruby так, чтобы все его ключи были символами?
у меня есть Руби хэш, который выглядит так:
{ "id" => "123", "name" => "test" }
Я хотел бы преобразовать его в:
{ :id => "123", :name => "test" }
15 ответов:
hash = {"apple" => "banana", "coconut" => "domino"} Hash[hash.map{ |k, v| [k.to_sym, v] }] #=> {:apple=>"banana", :coconut=>"domino"}
обновление:
@mu слишком короткий: не видел слова "рекурсивный", но если вы настаиваете (вместе с защитой от несуществующего
to_sym
, просто хочу напомнить, что в Ruby 1.81.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"}}
Если вам случится быть в рейки, то вы будете иметь
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
но он сохранит порядок, когда вы используете Ruby 1.9+. Вы также можете использовать[(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!
в хэш, если вы действительно хотели, но я обычно держусь подальше от исправления обезьян, если у меня нет очень веских причин для этого.
Если вы используете рельсы >= 4 Вы можете использовать:
hash.deep_symbolize_keys hash.deep_symbolize_keys!
или
hash.deep_stringify_keys hash.deep_stringify_keys!
на всякий случай вы разбираете JSON, от в JSON документы вы можете добавить опцию для обозначения ключей при разборе:
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)
Я предпочитаю:
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 }
вы также можете расширить основной хэш-класс 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!
начиная с 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.
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 и просто делает простой рекурсивный вызов, чтобы символизировать суб-хэши.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"}}