Ruby-on-rails 如何优雅地对“嵌套”散列进行符号化

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24927653/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 23:48:20  来源:igfitidea点击:

How to elegantly symbolize_keys for a 'nested' hash

ruby-on-railsrubyhashdry

提问by SHS

Consider the following code:

考虑以下代码:

  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 is a 'nested' hash i.e. a hash with string keys and similarly 'nested' hash values.

hash4 是一个“嵌套”散列,即带有字符串键和类似“嵌套”散列值的散列。

The 'symbolize_keys' method for Hash in Rails lets us easily convert the string keys to symbols. But I'm looking for an elegantway to convert all keys (primary keys plus keys of all hashes within hash4) to symbols.

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

The point is to save myself from my (imo) ugly solution:

关键是要从我的(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

FYI: Setup is Rails 3.2.17 and Ruby 2.1.1

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

Update:

更新:

Answer is hash4.deep_symbolize_keysfor Rails <= 5.0

答案是hash4.deep_symbolize_keysRails <= 5.0

Answer is JSON.parse(JSON[hash4], symbolize_names: true)for Rails > 5

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

回答by jvnill

There are a few ways to do this

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

  1. There's a deep_symbolize_keysmethod in Rails

    hash.deep_symbolize_keys!

  2. As mentioned by @chrisgeeq, there is a deep_transform_keysmethod that's available from Rails 4.

    hash.deep_transform_keys(&:to_sym)

    There is also a bang !version to replace the existing object.

  3. There is another method called with_indifferent_access. This allows you to access a hash with either a string or a symbol like how paramsare in the controller. This method doesn't have a bang counterpart.

    hash = hash.with_indifferent_access

  4. The last one is using JSON.parse. I personally don't like this because you're doing 2 transformations - hash to json then json to hash.

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

  1. Rails 中有一个deep_symbolize_keys方法

    hash.deep_symbolize_keys!

  2. 正如@chrisgeeq 所提到的,deep_transform_keysRails 4 提供了一种方法。

    hash.deep_transform_keys(&:to_sym)

    还有一个 bang!版本来替换现有的对象。

  3. 还有一种方法叫做with_indifferent_access. 这允许您使用字符串或符号访问哈希,就像params在控制器中一样。这种方法没有 bang 对应物。

    hash = hash.with_indifferent_access

  4. 最后一个是使用JSON.parse. 我个人不喜欢这个,因为你正在做 2 个转换 - 散列到 json 然后 json 到散列。

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

UPDATE:

更新:

16/01/19 - add more options and note deprecation of deep_symbolize_keys

2019 年 1 月 16 日 - 添加更多选项并注意弃用 deep_symbolize_keys

19/04/12 - remove deprecated note. only the implementation used in the method is deprecated, not the method itself.

2012 年 4 月 19 日 - 删除已弃用的注释。只有方法中使用的实现被弃用,而不是方法本身。

回答by Mikhail Chuprynski

You cannot use this method for params or any other instance of ActionController::Parametersany more, because deep_symbolize_keysmethod is deprecated in Rails 5.0+ due to security reasons and will be removed in Rails 5.1+ as ActionController::Parametersno longer inherits from Hash

您不能将此方法用于 params 或任何其他实例ActionController::Parameters,因为deep_symbolize_keys出于安全原因,该方法在 Rails 5.0+ 中已弃用,并将在 Rails 5.1+ 中删除,因为ActionController::Parameters不再继承自 Hash

So this approach by @Uri Agassi seems to be the universalone.

所以@Uri Agassi 的这种方法似乎是通用的

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


However, Rails Hash object still does have it.

然而,Rails Hash 对象仍然拥有它。

So options are:

所以选项是:

  • if you don't use Rails or just don't care:

    JSON.parse(JSON[h], symbolize_names: true)
    
  • with Rails and ActionController::Parameters:

    params.to_unsafe_h.deep_symbolize_keys
    
  • with Rails and plain Hash

    h.deep_symbolize_keys
    
  • 如果您不使用 Rails 或只是不在乎:

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

    params.to_unsafe_h.deep_symbolize_keys
    
  • 使用 Rails 和普通哈希

    h.deep_symbolize_keys
    

回答by Alexander

In rails you can create HashWithIndifferentAccess class. Create an instance of this class passing your hash to its constructor and then access it with keys that are symbols or strings (like params of Controller's Actions):

在 Rails 中,您可以创建 HashWithIndifferentAccess 类。创建此类的实例,将哈希传递给其构造函数,然后使用符号或字符串的键(如控制器操作的参数)访问它:

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

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

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

=> 3

回答by Uri Agassi

I can suggest something like this:

我可以提出这样的建议:

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} 

You can also easily extend it to support Arrays:

您还可以轻松扩展它以支持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"}]}

回答by mirage

Might I suggest:

我可以建议:

JSON.parse(hash_value.to_json)

回答by Cary Swoveland

You could use:

你可以使用:

  • Hash#to_sto convert the hash to a string;
  • String#gsubwith a regex to convert the keys from strings to representations of symbols; and then
  • Kernel#evalto convert the string back into a hash.
  • Hash#to_s将哈希值转换为字符串;
  • String#gsub使用正则表达式将键从字符串转换为符号表示;进而
  • Kernel#eval将字符串转换回散列。

This is an easy solution to your problem, but, you should only consider using it if you can trust that evalis not going to produce something nasty. If you have control over the content of the hash being converted, that should not be a problem.

这是您问题的简单解决方案,但是,只有在您相信eval不会产生令人讨厌的东西时才应该考虑使用它。如果您可以控制要转换的散列的内容,那应该不是问题。

This approach could be used for other kinds of nested objects, such as ones containing both arrays and hashes.

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

Code

代码

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

Examples

例子

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

Explanation

解释

(?==>)in the regex is a zero-width postive lookahead. ?=signifies positive lookahead; =>is the string that must immediately follow the match to \"(\w+)\". \1in ':\1'(or I could have written ":\\1") is a string beginning with a colon followed by a backreference to the content of capture group #1, the key matching \w+(without the quotes).

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