Как элегантно 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
Ответы
Ответ 1
Есть несколько способов сделать это
В Rails есть метод deep_symbolize_keys
hash.deep_symbolize_keys!
Как уже упоминалось @chrisgeeq, существует метод deep_transform_keys
, который доступен в Rails 4.
hash.deep_transform_keys(&:to_sym)
Существует также версия Bang !
для замены существующего объекта.
Есть еще один метод, который называется with_indifferent_access
. Это позволяет вам получить доступ к хешу с помощью строки или символа, например, как params
находится в контроллере. Этот метод не имеет аналогов.
hash = hash.with_indifferent_access
Последний использует 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+
(без кавычек).