Подтвердить что ты не робот

Как элегантно symbolize_keys для "вложенного" хэша

Рассмотрим следующий код:

  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 является "вложенным" хешем, т.е. хешем со строковыми ключами и аналогичными "вложенными" хеш-значениями.

Метод "symbolize_keys" для Hash in Rails позволяет нам легко преобразовывать строковые ключи в символы. Но я ищу способ элегантный преобразовать все ключи (первичные ключи плюс ключи всех хэшей в хэш-хэш) в символы.

Суть заключается в том, чтобы спасти себя от моего (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 - это Rails 3.2.17 и Ruby 2.1.1

Обновление:

Ответ hash4.deep_symbolize_keys для Rails <= 5.0

Ответ JSON.parse(JSON[hash4], symbolize_names: true) для Rails > 5

4b9b3361

Ответ 1

Есть несколько способов сделать это

  1. В Rails есть метод deep_symbolize_keys

    hash.deep_symbolize_keys!

  2. Как уже упоминалось @chrisgeeq, существует метод deep_transform_keys, который доступен в Rails 4.

    hash.deep_transform_keys(&:to_sym)

    Существует также версия Bang ! для замены существующего объекта.

  3. Есть еще один метод, который называется with_indifferent_access. Это позволяет вам получить доступ к хешу с помощью строки или символа, например, как params находится в контроллере. Этот метод не имеет аналогов.

    hash = hash.with_indifferent_access

  4. Последний использует JSON.parse. Мне лично это не нравится, потому что вы делаете 2 преобразования: хэш в json, затем json в hash.

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

UPDATE:

16/01/19 - добавьте больше опций и отметьте устаревание deep_symbolize_keys

19/04/12 - удалить устаревшую заметку. устарела только реализация, используемая в методе, а не сам метод.

Ответ 2

Вы не можете использовать этот метод для параметров или любого другого экземпляра ActionController::Parameters больше, потому что метод deep_symbolize_keys устарел в Rails 5.0+ из-за соображений безопасности и будет удален в Rails 5.1+ как ActionController::Parameters больше не будет наследует от Hash

Таким образом, этот подход by @Uri Agassi представляется универсальным.

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

Однако объект Rails Hash все еще имеет его.

Таким образом, параметры:

  • если вы не используете Rails или просто не заботитесь:

    JSON.parse(JSON[h], symbolize_names: true)
    
  • с Rails и ActionController:: Параметры:

    params.to_unsafe_h.deep_symbolize_keys
    
  • с Rails и простым хешем

    h.deep_symbolize_keys
    

Ответ 3

В рельсах вы можете создать класс HashWithIndifferentAccess. Создайте экземпляр этого класса, передающий ваш хэш своему конструктору, а затем получите его с помощью ключей, которые являются символами или строками (например, params of Actions 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

Ответ 4

Я могу предложить что-то вроде этого:

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

Ответ 5

Могу ли я предложить:

JSON.parse(hash_value.to_json)

Ответ 6

Вы можете использовать:

  • Hash # to_s, чтобы преобразовать хэш в строку;
  • String # gsub с регулярным выражением для преобразования ключей из строк в представления символов; и затем
  • Ядро # 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+ (без кавычек).