将json格式的键值对转换为以符号为键的ruby hash的最佳方法是什么?


102

我想知道将json格式的键值对转换为以符号为键的ruby hash的最佳方法是什么:示例:

{ '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

Answers:


255

在解析json字符串时使用json gem可以在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包含了这个库。
Simon Perepelitsa 2012年

这不是以前:symbolize_keys吗?为什么这个名字改变了?
卢卡斯2012年

5
@Lukas:symbolize_keys是Rails的东西。
wyattisimo 2013年

:symbolize_names虽然是Ruby的东西
fatuhoku

19

Leventix,谢谢您的回答。

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 2014年

5

并没有内置任何功能来实现该技巧,但是编写使用JSON gem来完成此操作的代码并不难。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

正如Leventix所说,JSON gem只处理双引号字符串(从技术上讲是正确的-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 gem,但是只能处理双引号。


正如madlep在下面说的-如果您知道JSON是有效的(例如,您要自己制作!),这就是您所需要的
edavey 2009年

这行不通。JSON.parse(JSON.generate([:a])) # => ["a"]
贾斯汀·L.2010年

2
那是因为JSON无法代表符号。您可以使用:Marshal.load(Marshal.dump([:a]))代替。
Leventix 2010年

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

@Rafe您是说2013年的这个安全漏洞今天仍然没有解决吗?
bert bruynooghe

1
自Ruby 2.2起,符号已进行GC处理。YAML.load用于序列化任意对象(例如,用于缓存)。该提议YAML.safe_load已在该博文发布后的几个月推出,因此只需要使用正确的方法即可:github.com/ruby/psych/commit/…–
Rafe

0

最方便的方法是使用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]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.