what is the best way to convert a json formatted key value pair to ruby hash with symbol as key?

I am wondering what is the best way to convert a json formatted key value pair to ruby hash with symbol as key: example:

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

Is there a helper method can do this?

76477 次浏览

当然,有一个 json gem,但它只处理双引号。

虽然没有内置任何东西来完成这个任务,但是使用 JSON gem 编写完成这项任务的代码并不太难。如果您使用的是 symbolize_keys方法,那么 Rails 中会内置一个 symbolize_keys方法,但是它并不像您需要的那样递归地表示键。

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

正如 Leventix 所说,JSON gem 只处理带双引号的字符串(这在技术上是正确的—— JSON 应该使用双引号格式)。这段代码将在尝试解析它之前清理它。

在解析 json 字符串时使用 json gem,您可以将该字符串传递给 symize _ Names 选项。请参见: http://flori.github.com/json/doc/index.html(在 parse 下查看)

例如:

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

递归方法:

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

Leventix 谢谢你的回答。

Load (Marshall. dump (h))方法可能是各种方法中最完整的,因为它保留了原始的键类型 递归

This is important in case you have a nested hash with a mix of string and symbol keys and you want to preserve that mix upon decode (for instance, this could happen if your hash contains your own custom objects in addition to highly complex/nested third-party objects whose keys you cannot manipulate/convert for whatever reason, like a project time constraint).

E.g.:

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"] } }

Unless there is a drawback that I'm unaware of, I'd think Method 3 is the way to go.

干杯

处理这个问题的另一种方法是使用 YAML 序列化/反序列化,它还保留了键的格式:

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

这种方法的好处似乎是更适合 REST 服务的格式..。

最方便的方法是使用 nice _ hash gem: 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]

如果您认为您可能同时需要字符串键和符号键:

JSON.parse(json_string).with_indifferent_access