Как преобразовать хэш Ruby так, чтобы все его ключи были символами?


у меня есть Руби хэш, который выглядит так:

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

Я хотел бы преобразовать его в:

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

15 ответов:

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"}}

Если вам случится быть в рейки, то вы будете иметь 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!

см.http://apidock.com/rails/v4.2.1/Hash/deep_symbolize_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"}}

вот небольшая рекурсивная функция для глубокой символизации ключей:

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