Qual é a melhor maneira de converter um par de valores-chave formatados em json em ruby ​​hash com o símbolo como chave?


103

Estou me perguntando qual é a melhor maneira de converter um par de valores-chave formatados em json para ruby ​​hash com o símbolo como chave: exemplo:

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

Existe um método auxiliar que pode fazer isso?


tente isso http://stackoverflow.com/a/43773159/1297435para rails 4.1
rails_id

Respostas:


256

usando a gema json ao analisar a string json, você pode passar a opção symbolize_names. Veja aqui: http://flori.github.com/json/doc/index.html (veja em análise)

por exemplo:

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

4
Ruby 1.9 inclui essa biblioteca, a propósito.
Simon Perepelitsa

não costumava ser :symbolize_keys? por que esse nome mudou?
Lukas

5
@Lukas: symbolize_keysé coisa do Rails.
wyattisimo de

: symbolize_names é uma coisa Ruby
fatuhoku

19

Leventix, obrigado pela sua resposta.

O método Marshal.load (Marshal.dump (h)) provavelmente tem a maior integridade dos vários métodos porque preserva os tipos de chave originais recursivamente .

Isso é importante no caso de você ter um hash aninhado com uma mistura de strings e chaves de símbolo e deseja preservar essa mistura na decodificação (por exemplo, isso pode acontecer se o seu hash contiver seus próprios objetos personalizados além do terceiro altamente complexo / aninhado -party objetos cujas chaves você não pode manipular / converter por qualquer motivo, como uma restrição de tempo do projeto).

Por exemplo:

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

Método 1 : JSON.parse - simboliza todas as chaves recursivamente => Não preserva a mistura original

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

Método 2 : ActiveSupport :: JSON.decode - simboliza apenas as chaves de nível superior => Não preserva a mistura original

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

Método 3 : Marshal.load - preserva a combinação original de string / símbolo nas chaves aninhadas. PERFEITO!

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

A menos que haja uma desvantagem da qual eu não saiba, acho que o Método 3 é o melhor.

Felicidades


2
Não há garantia aqui de que você tem controle do outro lado, então acredito que você deva se ater à formatação JSON. Se você tiver controle total de ambos os lados, Marshal é realmente um bom formato, mas não é adequado para serialização de uso geral.
chills42

5

Não há nada integrado para fazer o truque, mas não é muito difícil escrever o código para fazê-lo usando a gema JSON. Existe um symbolize_keysmétodo embutido no Rails se você estiver usando isso, mas não simboliza as chaves recursivamente como você precisa.

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

Como disse Leventix, a gema JSON só lida com strings entre aspas duplas (o que é tecnicamente correto - JSON deve ser formatado com aspas duplas). Este pedaço de código irá limpar isso antes de tentar analisá-lo.


4

Método recursivo:

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

Claro, existe uma gema json , mas que trata apenas de aspas duplas.


Como diz madlep abaixo - isso é tudo de que você precisa se souber que o JSON será válido (por exemplo, você mesmo está fazendo isso!)
edavey

Isso não funciona. JSON.parse(JSON.generate([:a])) # => ["a"]
Justin L.

2
Isso porque JSON não pode representar símbolos. Você pode usar: em Marshal.load(Marshal.dump([:a]))vez disso.
Leventix de

1

Outra maneira de lidar com isso é usar a serialização / desserialização YAML, que também preserva o formato da chave:

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

Benefício desta abordagem parece ser um formato mais adequado para serviços REST ...


Nunca deixe a entrada do usuário entrar em YAML.load: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Rafe

@Rafe, você quer dizer que essa falha de segurança de 2013 ainda não foi corrigida hoje?
bert bruynooghe

1
Símbolos são GC'ed desde Ruby 2.2. YAML.loaddestina-se a serializar objetos arbitrários (por exemplo, para cache). O propostoYAML.safe_load foi apresentada alguns meses depois daquela postagem no blog, então é uma questão de usar a coisa certa: github.com/ruby/psych/commit/…
Rafe

0

A maneira mais conveniente é usando a gem 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]
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.