请考虑以下代码:
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是一个'嵌套'哈希,即带有字符串键和类似“嵌套”哈希值的哈希。
Hails in Rails的'symbolize_keys'方法让我们可以轻松地将字符串键转换为符号。但我正在寻找一种优雅的方法将所有键(主键加上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
更新:
答案是hash4.deep_symbolize_keys
for Rails <= 5.0
答案是JSON.parse(JSON[hash4], symbolize_names: true)
for Rails> 5
有几种方法可以做到这一点
deep_symbolize_keys
method in Rails
hash.deep_symbolize_keys!
deep_transform_keys
方法。
hash.deep_transform_keys(&:to_sym)
还有一个爆炸!
版本来取代现有的对象。with_indifferent_access
的方法。这允许您使用字符串或符号来访问哈希,例如params
在控制器中的方式。这种方法没有爆炸对应物。
hash = hash.with_indifferent_access
JSON.parse
。我个人不喜欢这个,因为你正在进行2次转换 - 哈希到json然后json到哈希。
JSON.parse(JSON[h], symbolize_names: true)
更新:
16/01/19 - 添加更多选项并注意deep_symbolize_keys的弃用
19/04/12 - 删除弃用的备注。仅推荐使用方法中使用的实现,而不是方法本身。
您不能再将此方法用于params或ActionController::Parameters
的任何其他实例,因为出于安全原因,在Rails 5.0+中不推荐使用deep_symbolize_keys
方法,并且由于ActionController::Parameters
不再继承自Hash,因此将在Rails 5.1+中删除
所以@Uri Agassi的这种方法似乎是普遍的方法。
JSON.parse(JSON[h], symbolize_names: true)
但是,Rails Hash对象仍然拥有它。
所以选项是:
JSON.parse(JSON[h], symbolize_names: true)
params.to_unsafe_h.deep_symbolize_keys
h.deep_symbolize_keys
在rails中,您可以创建HashWithIndifferentAccess类。创建此类的实例,将您的哈希传递给它的构造函数,然后使用符号或字符串的键(如Controller的Actions的参数)访问它:
hash = {'a' => {'b' => [{c: 3}]}}
hash = hash.with_indifferent_access
# equal to:
# hash = ActiveSupport::HashWithIndifferentAccess.new(hash)
hash[:a][:b][0][:c]
=> 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"}]}
我可能会建议:
JSON.parse(hash_value.to_json)
你可以使用:
这是一个简单的解决方案,但是,如果你可以相信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的内容的反向引用,密钥匹配\w+
(没有引号)。