【问题标题】:How do I convert a Ruby hash so that all of its keys are symbols?如何转换 Ruby 哈希,使其所有键都是符号?
【发布时间】:2012-01-12 20:32:27
【问题描述】:

我有一个看起来像这样的 Ruby 哈希:

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

我想把它转换成:

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

【问题讨论】:

  • @IanVaughan 递归!
  • 另一种选择是使用 ActiveSupport 的HashWithIndifferentAccess。然后你就不必担心转换哈希,你可以使用一个符号来检索一个值,即使某些键是作为字符串插入的。如果你不使用 Rails,那么你必须include 'active_support/core_ext/hash/indifferent_access'

标签: ruby hash


【解决方案1】:
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"}}

【讨论】:

  • 我喜欢这个版本,只要按键懂to_sym。不过,您可能会被要求提供递归版本。
  • 如果数组中有散列,这不起作用:{"a"=>{"b"=>"c"}, "d"=>[{"f"= >"e"}]} 查看最后一个元素 "d"。我将尝试解决这个问题,但欢迎提供帮助/答案。我可以理解这可能不适用于其他对象,但我认为这样的数组很常见?
  • @nilanjan 在我看来不应该在这种情况下;如果需要该功能,我认为应该创建第二种方法,因为您基本上会将嵌套数组视为部分展平。
【解决方案2】:

如果您碰巧在 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 时更改哈希的内容,但在您使用 Ruby 1.9+ 时会保留顺序。您也可以使用 Rails 在其 symbolize_keys! 中使用的 [(key.to_sym rescue key) || key] 方法,但我认为这是对异常处理系统的滥用。

第二个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! 的任一版本修改为 Hash,但除非我有充分的理由这样做,否则我通常不会进行修改。

【讨论】:

  • +1 另外,如果@ed1t 没有使用 Rails,很容易看出它是在那里实现的
  • 有没有递归调用 symbolize_keys 的选项?
  • Rails API 中有一个递归变体,deep_symbolize_keys:api.rubyonrails.org/classes/…
  • 方法 symbolize_keys 已弃用,将在 Rails 5.1 中删除
  • 我认为您误读了@Rimian 的错误消息。那是在谈论尝试params.symbolize_keys(因此ActionController::Parameters),我没有看到任何证据表明Hash#symbolize_keys已被弃用,只是params.is_a? Hash在5.1中不会成立。
【解决方案3】:

如果您使用的是 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

【讨论】:

    【解决方案4】:

    以防万一您正在解析 JSON,您可以从 JSON docs 添加选项以在解析时符号化键:

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

    【讨论】:

    • 我实际上经常这样做:hash = JSON.parse(hash.to_json, symbolize_names: true).
    • 另外,这个答案不需要 Rails 来做。谢谢。
    【解决方案5】:

    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
    

    【讨论】:

      【解决方案6】:

      试试这个:

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

      这会遍历键,并且对于每个键,它都会删除字符串化键并将其值分配给符号化键。

      【讨论】:

      • 我在对象内部也有数组和散列。我怎样才能递归地应用这个?
      • 我喜欢这个答案,但我不明白点击的目的
      • 它们都产生相同的最终结果。但是,如果我们不使用#tap,那么表达式的值将是修改键的列表(h.keys),这有点令人困惑。我使用了#tap,所以结果是哈希而不是键数组。这仅用于说明目的。
      【解决方案7】:

      如果您使用的是 Rails(或只是 Active Support):

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

      【讨论】:

      • 或递归版本,deep_symbolize_keys
      • symbolize_keys 不是递归的。
      • 原题不需要递归。
      【解决方案8】:

      从 Ruby 2.5 开始,您可以使用 transform_key 方法。

      所以你的情况是:

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

      注意:Ruby on Rails 上也可以使用相同的方法。

      【讨论】:

      • 请注意,这不会递归到嵌套哈希中
      【解决方案9】:

      这是一个比所选答案更快的 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)
      

      【讨论】:

      • 只是好奇...k.intern 属性从何而来?你的 sn-p 因为它而不起作用,而且因为我不是专业的 Ruby-er,所以 sn-p 非常高效,很难理解。
      • k.intern 正在把钥匙变成符号。 k.intern 也等于 k.to_sym。我希望这能回答你的问题
      • 感谢您的澄清!你是完全正确的。我做了我应该做的事情(查看文档)并找到了String.intern 方法。它对我来说失败了,因为它在 symbol 上运行 k.intern 属性。 ruby-doc.org/core-2.2.0/String.html#method-i-intern
      【解决方案10】:

      我偏爱:

      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 }
      

      【讨论】:

      • 在这种情况下,each_with_object 可能比inject 更好(当然,除非each_with_object 不可用)。
      • @muistooshort,好吧,这几乎是两者之间的洗礼。我更喜欢inject,只是因为我总是使用它。从两者的来源来看,inject 更大,但这并不一定意味着更慢,或者足够慢以产生真正的影响。颠倒的参数和不需要返回h 值会导致我暂停使用它,那么使用each_with_object 有什么令人信服的理由吗?也许某个地方的基准测试显示inject 被吸烟?
      • 当我对块的返回值(a.inject(:+) 等)感兴趣时,我倾向于使用inject,而当我必须将; h 位添加到阻塞或者当我对“带着一些包袱进行迭代”而不是简单的注入感兴趣时。它们之间的块参数顺序差异有点反常。我发现块返回值在右侧视觉上丢失了。或者也许我只是固执己见:)
      • 不,你没有固执己见,我认为反转参数是一个糟糕的设计选择,但我们现在坚持下去。
      【解决方案11】:

      您还可以扩展核心 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!
      

      【讨论】:

      • 它也允许调用“symbolize_keys_deep!”在数组上也是如此!
      【解决方案12】:
      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}}}
      

      【讨论】:

        【解决方案13】:

        这是我的两分钱,

        我的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
        

        【讨论】:

          【解决方案14】:

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

          【讨论】:

            【解决方案15】:

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

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

            【讨论】:

              猜你喜欢
              • 2015-08-10
              • 2014-05-23
              • 2020-01-30
              • 1970-01-01
              • 2016-12-30
              • 1970-01-01
              • 2017-01-07
              • 2020-10-31
              • 2014-10-21
              相关资源
              最近更新 更多