який найкращий спосіб перетворити пару значень відформатованого ключа json в хеш-рубін із символом як ключем?


104

Мене цікавить, який найкращий спосіб перетворити пару значень відформатованого ключа json в хеш-рубін із символом як ключем: example:

{ '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для рейок 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


19

Левентікс, дякую за вашу відповідь.

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

Це важливо, якщо у вас є вкладений хеш із сумішшю рядків та символьних клавіш, і ви хочете зберегти цю суміш при декодуванні (наприклад, це може статися, якщо ваш хеш містить ваші власні власні об’єкти на додаток до дуже складних / вкладених третіх об'єкти-учасники, ключами яких ви не можете маніпулювати / перетворювати з будь-якої причини, наприклад обмеження часу проекту).

Наприклад:

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. Якщо ви маєте повний контроль з обох сторін, то маршал - це справді хороший формат, але він не підходить для загальної серіалізації.
озноб42

5

Немає нічого вбудованого, щоб зробити трюк, але написання коду не дуже важко, використовуючи дорогоцінний камінь JSON. Існує symbolize_keysметод, вбудований в Rails, якщо ви використовуєте це, але він не символізує ключі рекурсивно, як вам потрібно.

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]))натомість.
Левентікс

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 року досі не виправлена?
bert bruynooghe

1
Символи GC'ed з 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.