Jaki jest najlepszy sposób na przekonwertowanie pary klucz-wartość w formacie JSON na wartość mieszania ruby ​​z symbolem jako kluczem?

104

Zastanawiam się, jaki jest najlepszy sposób na przekonwertowanie pary klucza w formacie JSON na wartość hash ruby ​​z symbolem jako kluczem: przykład:

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

Czy istnieje metoda pomocnicza, która może to zrobić?

ez.
źródło
spróbuj tego http://stackoverflow.com/a/43773159/1297435dla szyn 4.1
rails_id

Odpowiedzi:

256

używając klejnotu json podczas analizowania łańcucha json można przekazać w opcji symbolize_names. Zobacz tutaj: http://flori.github.com/json/doc/index.html (spójrz pod parse)

na przykład:

>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"} 
jai
źródło
4
Nawiasem mówiąc, Ruby 1.9 zawiera tę bibliotekę.
Simon Perepelitsa
czy to nie było :symbolize_keys? dlaczego ta nazwa się zmieniła?
Lukas
5
@Lukas: symbolize_keysto sprawa Railsów.
wyattisimo
: symbolize_names to jednak kwestia Rubiego
fatuhoku
19

Leventix, dziękuję za odpowiedź.

Metoda Marshal.load (Marshal.dump (h)) prawdopodobnie ma największą integralność spośród różnych metod, ponieważ rekursywnie zachowuje oryginalne typy kluczy .

Jest to ważne w przypadku, gdy masz zagnieżdżony hash z kombinacją kluczy ciągów i symboli i chcesz zachować ten miks podczas dekodowania (na przykład może się to zdarzyć, jeśli twój hash zawiera własne niestandardowe obiekty oprócz bardzo złożonych / zagnieżdżonych trzecich obiekty firmowe, których kluczy nie można modyfikować / konwertować z jakiegokolwiek powodu, np. ograniczenie czasowe projektu).

Na przykład:

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

Metoda 1 : JSON.parse - rekurencyjnie symbolizuje wszystkie klucze => Nie zachowuje oryginalnego miksu

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

Metoda 2 : ActiveSupport :: JSON.decode - symbolizuje tylko klucze najwyższego poziomu => Nie zachowuje oryginalnego miksu

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

Metoda 3 : Marshal.load - zachowuje oryginalną kombinację ciągów / symboli w zagnieżdżonych kluczach. IDEALNY!

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

O ile nie ma wady, której nie jestem świadomy, myślę, że metoda 3 jest drogą do zrobienia.

Twoje zdrowie

szczery
źródło
2
Nie ma tutaj gwarancji, że masz kontrolę nad drugą stroną, więc uważam, że musisz trzymać się formatowania JSON. Jeśli masz pełną kontrolę nad obiema stronami, Marshal jest rzeczywiście dobrym formatem, ale nie nadaje się do serializacji ogólnego przeznaczenia.
dreszcze 42
5

Nie ma nic wbudowanego, aby załatwić sprawę, ale nie jest zbyt trudne napisanie kodu, aby zrobić to za pomocą klejnotu JSON. Jest symbolize_keysmetoda wbudowana w Railsy, ​​jeśli tego używasz, ale nie symbolizuje ona rekursywnych kluczy, tak jak potrzebujesz.

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

Jak powiedział Leventix, klejnot JSON obsługuje tylko ciągi znaków w podwójnych cudzysłowach (co jest technicznie poprawne - JSON powinien być sformatowany w cudzysłowy). Ten fragment kodu wyczyści to przed próbą przeanalizowania.

madlep
źródło
4

Metoda rekurencyjna:

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
Oel Roc
źródło
1

Oczywiście istnieje klejnot json , ale obsługuje on tylko podwójne cudzysłowy.

Leventix
źródło
Jak mówi madlep poniżej - to wszystko, czego potrzebujesz, jeśli wiesz, że JSON będzie ważny (np.
Robisz
To nie działa. JSON.parse(JSON.generate([:a])) # => ["a"]
Justin L.,
2
Dzieje się tak, ponieważ JSON nie może reprezentować symboli. Możesz użyć: Marshal.load(Marshal.dump([:a]))zamiast.
Leventix,
1

Innym sposobem rozwiązania tego problemu jest użycie serializacji / deserializacji YAML, która również zachowuje format klucza:

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

Korzyści z tego podejścia wydają się formatem, który jest lepiej dostosowany do usług REST ...

bert bruynooghe
źródło
Nigdy nie pozwól, aby dane wejściowe użytkownika trafiały do ​​YAML.load: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Rafe
@Rafe, czy masz na myśli, że ta luka w zabezpieczeniach z 2013 roku nadal nie została naprawiona?
bert bruynooghe
1
Symbole są oznaczone GC od wersji Ruby 2.2. YAML.loadjest przeznaczony do serializacji dowolnych obiektów (np. dla pamięci podręcznej). Propozycja YAML.safe_loadzostała wprowadzona kilka miesięcy po tym poście na blogu, więc jest to kwestia właściwego użycia: github.com/ruby/psych/commit/…
Rafe
0

Najwygodniej jest użyć klejnotu 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]
Mario Ruiz
źródło