如何优雅地符号化_keys嵌套'哈希

时间:2014-07-24 07:33:53

标签: ruby-on-rails ruby hash dry

请考虑以下代码:

  hash1 = {"one" => 1, "two" => 2, "three" => 3}
  hash2 = hash1.reduce({}){ |h, (k,v)| h.merge(k => hash1) }
  hash3 = hash2.reduce({}){ |h, (k,v)| h.merge(k => hash2) }
  hash4 = hash3.reduce({}){ |h, (k,v)| h.merge(k => hash3) }

hash4是一个'嵌套' hash,即带有字符串键的哈希,类似地嵌套'哈希值。

' symbolize_keys' Rails中的Hash方法让我们可以轻松地将字符串键转换为符号。但我正在寻找一种优雅的方式将所有键(主键加上hash4中所有哈希的键)转换为符号。

重点是从我的( imo )丑陋的解决方案中拯救自己:

  class Hash
    def symbolize_keys_and_hash_values
      symbolize_keys.reduce({}) do |h, (k,v)|
        new_val = v.is_a?(Hash) ? v.symbolize_keys_and_hash_values : v
        h.merge({k => new_val})
      end
    end
  end

  hash4.symbolize_keys_and_hash_values #=> desired result

仅供参考:安装程序是Rails 3.2.17和Ruby 2.1.1

更新:

对于Rails< = 5.0

,答案为hash4.deep_symbolize_keys

对于Rails,答案是JSON.parse(JSON[hash4], symbolize_names: true)> 5

6 个答案:

答案 0 :(得分:90)

有几种方法可以做到这一点

  1. deep_symbolize_keys method in Rails

    hash.deep_symbolize_keys!

  2. 正如@chrisgeeq所提到的,Rails 4提供了deep_transform_keys方法。

    hash.deep_transform_keys(&:to_sym)

    还有一个爆炸!版本来替换现有对象。

  3. 还有另一种名为with_indifferent_access的方法。这允许您使用字符串或符号来访问哈希,例如控制器中的params。这种方法没有爆炸性的对应物。

    hash = hash.with_indifferent_access

  4. 最后一个使用JSON.parse。我个人不喜欢这样,因为你正在进行2次转换 - 哈希到json然后json到哈希。

    JSON.parse(JSON[h], symbolize_names: true)

  5. 更新:

    16/01/19 - 添加更多选项并注意deep_symbolize_keys的弃用

    19/04/12 - 删除弃用的备注。只推荐使用该方法中使用的实现,而不是方法本身。

答案 1 :(得分:25)

您不能再将此方法用于params或ActionController::Parameters的任何其他实例,因为出于安全原因,Rails 5.0+中不推荐使用deep_symbolize_keys方法,并且将在Rails 5.1+中删除{{ {1}}不再 继承自Hash

所以@Uri Agassi 的这种方法似乎是普遍的

ActionController::Parameters

但是,Rails哈希对象仍然拥有它。

所以选项是:

  • 如果您不使用Rails或只是不关心:

    JSON.parse(JSON[h], symbolize_names: true)
    
  • 使用Rails和ActionController ::参数:

    JSON.parse(JSON[h], symbolize_names: true)
    
  • 使用Rails和普通哈希

    params.to_unsafe_h.deep_symbolize_keys
    

答案 2 :(得分:6)

在rails中,您可以创建HashWithIndifferentAccess类。创建此类的实例,将您的哈希值传递给它的构造函数,然后使用符号或字符串的键访问它(比如Controller&#39的动作的参数):

hash = {'a' => {'b' => [{c: 3}]}}

hash = hash.with_indifferent_access
# equal to:
# hash = ActiveSupport::HashWithIndifferentAccess.new(hash)

hash[:a][:b][0][:c]

=> 3

答案 3 :(得分:3)

我可以建议这样的事情:

class Object
  def deep_symbolize_keys
    self
  end
end

class Hash
  def deep_symbolize_keys
    symbolize_keys.tap { |h| h.each { |k, v| h[k] = v.deep_symbolize_keys } }
  end
end

{'a'=>1, 'b'=>{'c'=>{'d'=>'d'}, e:'f'}, 'g'=>1.0, 'h'=>nil}.deep_symbolize_keys
# => {:a=>1, :b=>{:c=>{:d=>"d"}, :e=>"f"}, :g=>1.0, :h=>nil} 

您也可以轻松地将其扩展为支持Arrays

class Array
  def deep_symbolize_keys
    map(&:deep_symbolize_keys)
  end
end

{'a'=>1, 'b'=>[{'c'=>{'d'=>'d'}}, {e:'f'}]}.deep_symbolize_keys
# => {:a=>1, :b=>[{:c=>{:d=>"d"}}, {:e=>"f"}]}

答案 4 :(得分:2)

您可以使用:

  • Hash#to_s将哈希值转换为字符串;
  • String#gsub使用正则表达式将键从字符串转换为符号表示;然后
  • Kernel#eval将字符串转换回哈希值。

这是解决您问题的简单方法,但是,如果您相信eval不会产生令人讨厌的东西,则应该只考虑使用它。如果您可以控制正在转换的哈希的内容,那应该不是问题。

此方法可用于其他类型的嵌套对象,例如包含数组和散列的嵌套对象。

<强>代码

def symbolize_hash(h)
  eval(h.to_s.gsub(/\"(\w+)\"(?==>)/, ':\1'))
end

<强>实施例

symbolize_hash(hash4)
  #=> {:one=>{:one=>  {:one=>  {:one=>1, :two=>2, :three=>3},
  #                    :two=>  {:one=>1, :two=>2, :three=>3},
  #                    :three=>{:one=>1, :two=>2, :three=>3}},
  #           :two=>  {:one=>  {:one=>1, :two=>2, :three=>3},
  #                    :two=>  {:one=>1, :two=>2, :three=>3},
  #                    :three=>{:one=>1, :two=>2, :three=>3}},
  #           :three=>{:one=>  {:one=>1, :two=>2, :three=>3},
  #                    :two=>  {:one=>1, :two=>2, :three=>3},
  #                    :three=>{:one=>1, :two=>2, :three=>3}}},
  #    :two=>{:one=>  {:one=>  {:one=>1, :two=>2, :three=>3},
  #    ...
  #    :three=>{:one=>{:one=>  {:one=>1, :two=>2, :three=>3},
  #    ...
  #                    :three=>{:one=>1, :two=>2, :three=>3}}}}

symbolize_hash({'a'=>1, 'b'=>[{'c'=>{'d'=>'d'}}, {e:'f'}]})
  #=> {:a=>1, :b=>[{:c=>{:d=>"d"}}, {:e=>"f"}]}

<强>解释

正则表达式中的

(?==>)是零宽度的正面预测。 ?=表示积极前瞻; =>是必须紧跟匹配到\"(\w+)\"的字符串。 \1中的':\1'(或者我可以编写":\\1")是一个以冒号开头的字符串,后面是对捕获组#1内容的反向引用,键匹配{{1} (不带引号)。

答案 5 :(得分:1)

我可以建议:

JSON.parse(hash_value.to_json)