каков наилучший способ преобразовать пару значений ключа в формате json в рубиновый хеш с символом в качестве ключа?


104

Мне интересно, как лучше всего преобразовать пару значений ключа в формате json в рубиновый хеш с символом в качестве ключа: пример:

{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }
==> 
{ :user=>{ :name => 'foo', :age =>'40', :location=>{ :city => 'bar', :state=>'ca' } } }

Есть ли вспомогательный метод, который может это сделать?


попробуйте это http://stackoverflow.com/a/43773159/1297435для rails 4.1
rails_id

Ответы:


256

используя гем json при разборе строки json, вы можете передать параметр symbolize_names. См. Здесь: http://flori.github.com/json/doc/index.html (смотрите под синтаксическим анализом)

например:

>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"} 

4
Кстати, Ruby 1.9 включает эту библиотеку.
Симон Перепелица

разве это не было раньше :symbolize_keys? почему это имя изменилось?
Лукас

5
@Lukas: symbolize_keysэто вещь из Rails.
wyattisimo

: symbolize_names - это
тоже

19

Левентикс, спасибо за ответ.

Метод Marshal.load (Marshal.dump (h)), вероятно, имеет наибольшую целостность среди различных методов, поскольку он рекурсивно сохраняет исходные типы ключей .

Это важно, если у вас есть вложенный хэш со смесью строковых и символьных ключей, и вы хотите сохранить это сочетание при декодировании (например, это может произойти, если ваш хеш содержит ваши собственные пользовательские объекты в дополнение к очень сложным / вложенным третьим -party объекты, ключи которых вы не можете манипулировать / преобразовывать по какой-либо причине, например временные ограничения проекта).

Например:

h = {
      :youtube => {
                    :search   => 'daffy',                 # nested symbol key
                    'history' => ['goofy', 'mickey']      # nested string key
                  }
    }

Метод 1 : JSON.parse - рекурсивно символизирует все ключи => Не сохраняет исходный микс

JSON.parse( h.to_json, {:symbolize_names => true} )
  => { :youtube => { :search=> "daffy", :history => ["goofy", "mickey"] } } 

Метод 2 : ActiveSupport :: JSON.decode - символизирует только ключи верхнего уровня => Не сохраняет исходный микс

ActiveSupport::JSON.decode( ActiveSupport::JSON.encode(h) ).symbolize_keys
  => { :youtube => { "search" => "daffy", "history" => ["goofy", "mickey"] } }

Метод 3 : Marshal.load - сохраняет исходное сочетание строки / символа во вложенных ключах. ИДЕАЛЬНЫЙ!

Marshal.load( Marshal.dump(h) )
  => { :youtube => { :search => "daffy", "history" => ["goofy", "mickey"] } }

Если нет недостатка, о котором я не знаю, я бы подумал, что метод 3 - это правильный путь.

Ура


2
Здесь нет гарантии, что вы контролируете другую сторону, поэтому я считаю, что вам нужно придерживаться форматирования JSON. Если у вас есть полный контроль над обеими сторонами, то Marshal действительно хороший формат, но он не подходит для сериализации общего назначения.
chills42

5

Нет ничего встроенного, чтобы сделать это, но написать код для этого с использованием драгоценного камня JSON не так уж сложно. В symbolize_keysRails есть метод, если вы его используете, но он не символизирует ключи рекурсивно, как вам нужно.

require 'json'

def json_to_sym_hash(json)
  json.gsub!('\'', '"')
  parsed = JSON.parse(json)
  symbolize_keys(parsed)
end

def symbolize_keys(hash)
  hash.inject({}){|new_hash, key_value|
    key, value = key_value
    value = symbolize_keys(value) if value.is_a?(Hash)
    new_hash[key.to_sym] = value
    new_hash
  }
end

Как сказал Левентикс, гем JSON обрабатывает только строки с двойными кавычками (что технически правильно - JSON должен быть отформатирован с двойными кавычками). Этот фрагмент кода очистит это, прежде чем пытаться его проанализировать.


4

Рекурсивный метод:

require 'json'

def JSON.parse(source, opts = {})
  r = JSON.parser.new(source, opts).parse
  r = keys_to_symbol(r) if opts[:symbolize_names]
  return r
end

def keys_to_symbol(h)
  new_hash = {}
  h.each do |k,v|
    if v.class == String || v.class == Fixnum || v.class == Float
      new_hash[k.to_sym] = v
    elsif v.class == Hash
      new_hash[k.to_sym] = keys_to_symbol(v)
    elsif v.class == Array
      new_hash[k.to_sym] = keys_to_symbol_array(v)
    else
      raise ArgumentError, "Type not supported: #{v.class}"
    end
  end
  return new_hash
end

def keys_to_symbol_array(array)
  new_array = []
  array.each do |i|
    if i.class == Hash
      new_array << keys_to_symbol(i)
    elsif i.class == Array
      new_array << keys_to_symbol_array(i)
    else
      new_array << i
    end
  end
  return new_array
end

1

Конечно, есть гем json , но он обрабатывает только двойные кавычки.


Как говорит Мадлеп ниже - это все, что вам нужно, если вы знаете, что JSON будет действителен (например, вы делаете его сами!)
edavey

Это не работает. JSON.parse(JSON.generate([:a])) # => ["a"]
Джастин Л.

2
Это потому, что JSON не может представлять символы. Вы можете использовать: Marshal.load(Marshal.dump([:a]))вместо.
Leventix

1

Другой способ справиться с этим - использовать сериализацию / десериализацию YAML, которая также сохраняет формат ключа:

YAML.load({test: {'test' => { ':test' => 5}}}.to_yaml) 
=> {:test=>{"test"=>{":test"=>5}}}

Преимущество этого подхода кажется, что формат лучше подходит для служб REST ...


Никогда не позволяйте пользователя ввода войти в YAML.load: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Рейф

@Rafe, вы имеете в виду, что эта дыра в безопасности 2013 года до сих пор не исправлена?
Берт Брюнооге

1
Символы создаются с помощью GC, начиная с Ruby 2.2. YAML.loadпредназначен для сериализации произвольных объектов (например, для кеша). Предлагаемое YAML.safe_loadбыло представлено через несколько месяцев после этого сообщения в блоге, так что это вопрос правильного использования: github.com/ruby/psych/commit/…
Rafe

0

Самый удобный способ - использовать гем nice_hash: https://github.com/MarioRuiz/nice_hash

require 'nice_hash'
my_str = "{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }"

# on my_hash will have the json as a hash
my_hash = my_str.json

# or you can filter and get what you want
vals = my_str.json(:age, :city)

# even you can access the keys like this:
puts my_hash._user._location._city
puts my_hash.user.location.city
puts my_hash[:user][:location][:city]
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.