web-dev-qa-db-ja.com

「ネストされた」ハッシュのエレガントなシンボル化方法

次のコードを検討してください。

_  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は「ネストされた」ハッシュ、つまり文字列キーと同様に「ネストされた」ハッシュ値を持つハッシュです。

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 and Ruby 2.1.1

更新:

答えは_hash4.deep_symbolize_keys_ Rails <= 5.0

答えはJSON.parse(JSON[hash4], symbolize_names: true) for Rails> 5

46
SHS

これを行うにはいくつかの方法があります

  1. _deep_symbolize_keys_ Railsのメソッド があります

    _hash.deep_symbolize_keys!_

  2. @chrisgeeqで述べたように、Rails 4から利用できる _deep_transform_keys_ メソッドがあります。

    hash.deep_transform_keys(&:to_sym)

    既存のオブジェクトを置き換えるbang _!_バージョンもあります。

  3. _with_indifferent_access_ という別のメソッドがあります。これにより、コントローラー内でのparamsのような文字列または記号を使用してハッシュにアクセスできます。このメソッドには、対応する強打がありません。

    _hash = hash.with_indifferent_access_

  4. 最後のものは_JSON.parse_を使用しています。あなたは2つの変換をしているので、私は個人的にこれが好きではありません-jsonへのハッシュとハッシュへのjson。

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

更新:

16/01/19-さらにオプションを追加し、deep_symbolize_keysの非推奨に注意

19/04/12-非推奨のメモを削除。メソッド自体ではなく、メソッドで使用される実装のみが非推奨です。

95
jvnill

ActionController::Parametersメソッドは、セキュリティ上の理由によりRails 5.0+で非推奨になり、Rails 5.1+では削除されるため、このメソッドをparamまたはその他のdeep_symbolize_keysのインスタンスに使用することはできません。 ActionController::ParametersはHashを継承しなくなりました

@Uri Agassiによるこのアプローチユニバーサルのようです one。

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

ただし、Rails Hashオブジェクトにはまだあります。

オプションは次のとおりです。

  • Railsを使用しない場合、または単に気にしない場合:

    JSON.parse(JSON[h], symbolize_names: true)
    
  • RailsおよびActionController :: Parametersを使用:

    params.to_unsafe_h.deep_symbolize_keys
    
  • Railsおよびプレーンハッシュ

    h.deep_symbolize_keys
    
25

In Rails HashWithIndifferentAccessクラスを作成できます。ハッシュをコンストラクタに渡すこのクラスのインスタンスを作成し、シンボルまたは文字列(ControllerのActionのparamsなど)でキーにアクセスします):

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

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

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

=> 3
6
Alexander

私はこのようなものを提案することができます:

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"}]}
3
Uri Agassi

私が提案するかもしれません:

JSON.parse(hash_value.to_json)
2
mirage

次を使用できます。

  • 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のコンテンツへの後方参照が続く文字列で、キーは\w+に一致します(引用符なし)。

1
Cary Swoveland