Ruby处理YAML和json数据


Posted in Ruby onApril 18, 2022

Ruby处理YAML

Ruby的标准库YAML基于Psych:https://ruby-doc.org/stdlib-2.6.2/libdoc/psych/rdoc/Psych.html

require 'yaml' 之后,为大多数的基本数据类型都提供了 to_ yaml() 方法,用于将各数据类型的对象转换为yaml格式。

例如:

require 'yaml'
require 'set'

p "hello world".to_yaml
p 123.to_yaml
p %w(perl shell php).to_yaml
p ({one: 1, two: 2}).to_yaml
p Set.new([1,2,3]).to_yaml

得到:

"--- hello world\n"
"--- 123\n"
"---\n- perl\n- shell\n- php\n"
"---\n:one: 1\n:two: 2\n"
"--- !ruby/object:Set\nhash:\n  1: true\n  2: true\n  3: true\n"

也可以使用YAML.dump()方法实现和to_yaml相同的功能,它还可以写入文件。

users = [{name: 'Bob', permissions: ['Read']},
 {name: 'Alice', permissions:['Read', 'Write']}]

File.open("/tmp/a.yml","w") { |f| YAML.dump(users, f) }

查看文件:

---
- :name: Bob             #=> 注意,保留了hash源数据中的符号
  :permissions:
  - Read
- :name: Alice
  :permissions:
  - Read
  - Write

用YAML.load()从YAML中读取数据:

require 'yaml'

pp YAML.load(DATA)

__END__
mysql:
  passwd: P@ssword1!
  user: root
  port: 3306
  other1: nil
  other2: false
  other3: ""
  hosts: 
    - ip: 10.10.1.1
      hostname: node1
    - ip: 10.10.1.2
      hostname: node2

得到:

{"mysql"=>
  {"passwd"=>"P@ssword1!",      #=> 注意,key是String而非Symbol
   "user"=>"root",
   "port"=>3306,
   "other1"=>"nil",
   "other2"=>false,
   "other3"=>"",
   "hosts"=>
    [{"ip"=>"10.10.1.1", "hostname"=>"node1"},
     {"ip"=>"10.10.1.2", "hostname"=>"node2"}]}}

如果想让hash的key是符号而非字符串,可以设置选项symbolize_names: true

pp YAML.load(DATA, symbolize_names: true)

需要注意,YAML可以将对象进行序列化,所以有几方面注意事项:

  • 在反序列化的时候需要也require涉及到的文件,例如对Set类型序列化后,在反序列化时如不require 'set'则无法还原对象
  • 有些底层对象不能序列化,包括IO流、Ruby代码对象Proc、Binding等
  • 不要反序列化不被信任的数据对象(比如用户输入的数据),此时可使用safe_load(),它默认只允许加载以下几种类型的数据:
    • TrueClass
    • FalseClass
    • NilClass
    • Numeric
    • String
    • Array
    • Hash
  • 如果确实想要加载额外的数据类型,可以在safe_load()中指定参数permitted_classes: []或permitted_symbols: []

Ruby处理Json数据

转为json格式字符串

使用JSON.generate()可以将对象或数组转换为JSON格式的数据:

require 'json'
p JSON.generate "abc"
p JSON.generate 123
p JSON.generate true
p JSON.generate nil
p JSON.generate [2,3,4]
p JSON.generate({name: "junmajinlong", age: 23})
require 'set'
p JSON.generate(Set.new([1,23,44]))

得到:

"\"abc\""
"123"
"true"
"null"
"[2,3,4]"
"{\"name\":\"junmajinlong\",\"age\":23}"
"\"#<Set: {1, 23, 44}>\""

require 'json'后,很多ruby类型都具备了一个to_json的方法,可以直接将该类型的数据转换为json数据:

p ({name: "junmajinlong", age: 23}).to_json
p (Set.new([1,23,44])).to_json

得到:

"{\"name\":\"junmajinlong\",\"age\":23}"
"\"#<Set: {1, 23, 44}>\""

此外,JSON.dump()也可以将对象转换为JSON格式的字符串,而且它还支持写入文件:

hsh = {name: "junmajinlong", age: 23}
File.open("/tmp/a.json", "w") {|f| JSON.dump(hsh, f)}

json格式字符串转为Ruby对象

要从json格式字符串转为ruby对象,有一些选项可设置,参考https://ruby-doc.org/stdlib-2.7.1/libdoc/json/rdoc/JSON.html#method-i-parse,比如*symbolize_names*选项表示是否将json object中的key解析为符号类型的key,如果设置为false,则解析为字符串的key。

要将json格式的字符串解析为Ruby数据类型(Hash),使用JSON.parse()

require 'json'

hsh = '{"name": "junmajinlong", "age": 23}'

p JSON.parse(hsh)
p JSON.parse(hsh, symbolize_names: true)

注意,上面的json字符串必须是合理的json数据,比如key必须使用双引号包围而不能使用单引号,字符串必须使用双引号包围,等等。比如"{'name': 'junmajinlong', 'age': 23}"就不是合理的json字符串。

要从json文件中读取json数据并转换为Ruby数据,使用load():

data = File.open("/tmp/a.json") do |f|
  JSON.load(f)
end

pp data
#=> {"name"=>"junmajinlong", "age"=>23}

自定义对象的转换方式

json支持的数据类型有:

  • 字符串
  • 数值
  • 对象
  • 数组
  • 布尔
  • Null

从一种语言的数据转换为Json数据时,如果数据类型也是JSON所支持的,可直接转换,但如果包含了JSON不支持的类型,则可能报错,也可能以一种对象字符串的方式保存,这取决于对应的实现。

可以在对象中定义as_json实例方法来决定对象如何转换为json字符串,再定义类方法from_json()来决定如何从json字符串中恢复为一个对象。

例如,

require 'json'
require 'date'

class Person
  attr_accessor :name, :birthday
  def initialize name, birthday
    @name = name
    @birthday = DateTime.parse(birthday)
  end
end

File.open("/tmp/p.json", "w") do |f|
  JSON.dump(Person.new("junmajinlong", "1999-10-11"), f)
end

查看保存的json数据:

$ cat /tmp/p.json
"#<Person:0x00007fffc7e575d0>"

定义as_jsonfrmo_json

require 'json'
require 'date'

class Person
  attr_accessor :name, :birthday
  
  def initialize name, birthday
    @name = name
    @birthday = DateTime.parse(birthday)
  end
  
  def as_json
    {
      name: @name,
      birthday: @birthday.strftime("%F")
    }
  end

  def self.from_json json
    data = JSON.parse(json)
    new(data["name"], data["birthday"])
  end
end

之后要序列化、反序列化该对象,可:

data = Person.new("junmajinlong", "1999-10-11").as_json
p data

p1=Person.from_json(JSON.dump data)
p p1.birthday

如果是读写json文件,可:

person1 = Person.new("junmajinlong", "1999-10-11")
File.open("/tmp/p.json", "w") do |f|
  JSON.dump(person1.as_json, f)
end

p1 = File.open("/tmp/p.json") do |f|
  Person.from_json(f.read)
  # Person.from_json(JSON.load(f).to_json)
end
p p1

几种JSON解析工具的性能测试

测试了json标准库、oj和fast_josnparser解析json的性能,测试项包括:

  • 从文件中加载并解析json字符串为ruby对象
  • 从内存json字符串中解析json字符串为ruby对象
  • 带有symbolize_keys/symbolize_names转换时的解析
  • json标准库和oj将ruby对象dump为json字符串
  • json标准库和oj将ruby对象dump为json字符串保存到文件

注:

  • fast_jsonparser没有dump功能,只有解析json字符串功能
  • oj在将对象转换为json字符串时,可能会丢失数据的精度,比如浮点数的精度

测试的json字符串数量大约50M。

测试了ruby 2.7.1和ruby 3.0.1两个版本,gem包的版本信息如下:

fast_jsonparser (0.5.0)
json (default: 2.5.1)
oj (3.11.7)

测试代码:

require 'benchmark'
require 'json'
require 'oj'
require 'fast_jsonparser'

# warm
json_file='test'  # 文件大小大约50M
str = File.read(json_file)

######## JSON

puts " load file ".center(80, '-')
Benchmark.bm(30) do |x|
  x.report("JSON.load:") { File.open(json_file){ |f| JSON.load(f) } }
  x.report("Oj.load_file:") { Oj.load_file(json_file) }
  x.report("FastJsonparser.load:") { FastJsonparser.load(json_file) }
end

puts
puts " load file with symbolize_keys ".center(80, '-')
Benchmark.bm(30) do |x|
  x.report("JSON.load:") { File.open(json_file){ |f| JSON.load(f, nil, symbolize_names: true, create_additions: false) } }
  x.report("Oj.load_file:") { Oj.load_file(json_file, symbol_keys: true) }
  x.report("FastJsonparser.load:") { FastJsonparser.load(json_file, symbolize_keys: true) }
end

puts
puts " parse str ".center(80, '-')
Benchmark.bm(30) do |x|
  x.report("JSON.parse:") { JSON.parse(str) }
  x.report("Oj.load:") { Oj.load(str) }
  x.report("FastJsonparser.parse:") { FastJsonparser.parse(str) }
end

puts
puts " parse str with symbolize_keys ".center(80, '-')
Benchmark.bm(30) do |x|
  x.report("JSON.parse:") { JSON.parse(str, symbolize_names: true) }
  x.report("Oj.load:") { Oj.load(str, symbol_keys: true) }
  x.report("FastJsonparser.parse:") { FastJsonparser.parse(str, symbolize_keys: true) }
end

obj = JSON.parse(str, symbolize_names: true)

puts 
puts " dump JSON to str ".center(80, '-')
Benchmark.bm(30) do |x|
  x.report("JSON.dump:") { JSON.dump(obj) }
  x.report("Oj.dump:") { Oj.dump(obj) }
end

puts 
puts " dump JSON to file ".center(80, '-')
Benchmark.bm(30) do |x|
  x.report("JSON.dump:") { File.open('0_json_dump', 'w') {|f| JSON.dump(obj, f) } }
  x.report("Oj.to_file:") { Oj.to_file('0_oj_dump', obj) }
end

测试结果:

Ruby 2.7.1中:

---------------------------------- load file -----------------------------------
                                     user     system      total        real
JSON.load:                       1.591831   0.058021   1.649852 (  1.738119)
Oj.load_file:                    1.350385   0.057684   1.408069 (  2.434268) <-慢
FastJsonparser.load:             0.653968   0.103258   0.757226 (  0.848913) <-快

------------------------ load file with symbolize_keys -------------------------
                                     user     system      total        real
JSON.load:                       1.212617   0.039052   1.251669 (  1.349545)
Oj.load_file:                    1.432059   0.098950   1.531009 (  2.679610) <-慢
FastJsonparser.load:             0.695538   0.008384   0.703922 (  0.797081) <-快

---------------------------------- parse str -----------------------------------
                                     user     system      total        real
JSON.parse:                      1.343596   0.000000   1.343596 (  1.350368)
Oj.load:                         1.133612   0.000000   1.133612 (  1.140939)
FastJsonparser.parse:            0.701701   0.012340   0.714041 (  0.720296) <-快

------------------------ parse str with symbolize_keys -------------------------
                                     user     system      total        real
JSON.parse:                      1.250775   0.000000   1.250775 (  1.258796)
Oj.load:                         1.131296   0.000000   1.131296 (  1.138020)
FastJsonparser.parse:            0.697433   0.015962   0.713395 (  0.719439) <-快

------------------------------- dump JSON to str -------------------------------
                                     user     system      total        real
JSON.dump:                       1.374611   0.028454   1.403065 (  1.403081)
Oj.dump:                         1.025049   0.040184   1.065233 (  1.065246) <-快

------------------------------ dump JSON to file -------------------------------
                                     user     system      total        real
JSON.dump:                       1.234362   0.040246   1.274608 (  1.369214)
Oj.to_file:                      1.168707   0.000000   1.168707 (  1.270957)

Ruby 3.0.1中:

---------------------------------- load file -----------------------------------
                                     user     system      total        real
JSON.load:                       1.362151   0.083610   1.445761 (  1.569754)
Oj.load_file:                    1.343601   0.182046   1.525647 (  2.684472) <-慢
FastJsonparser.load:             2.634435   0.052734   2.687169 (  2.776105) <-慢

------------------------ load file with symbolize_keys -------------------------
                                     user     system      total        real
JSON.load:                       1.287954   0.018572   1.306526 (  1.409770)
Oj.load_file:                    1.478750   0.043847   1.522597 (  2.668882) <-慢
FastJsonparser.load:             2.717857   0.006164   2.724021 (  2.822728) <-慢

---------------------------------- parse str -----------------------------------
                                     user     system      total        real
JSON.parse:                      1.242225   0.008661   1.250886 (  1.304554)
Oj.load:                         1.097922   0.000000   1.097922 (  1.110031)
FastJsonparser.parse:            2.602679   0.017232   2.619911 (  2.634604) <-慢

------------------------ parse str with symbolize_keys -------------------------
                                     user     system      total        real
JSON.parse:                      1.368262   0.000000   1.368262 (  1.380730)
Oj.load:                         1.332349   0.000000   1.332349 (  1.346331)
FastJsonparser.parse:            2.706804   0.007238   2.714042 (  2.726935) <-慢

------------------------------- dump JSON to str -------------------------------
                                     user     system      total        real
JSON.dump:                       1.724653   0.009250   1.733903 (  1.733912)
Oj.dump:                         1.298235   0.030041   1.328276 (  1.328279) <-快

------------------------------ dump JSON to file -------------------------------
                                     user     system      total        real
JSON.dump:                       1.765664   0.040595   1.806259 (  1.905785)
Oj.to_file:                      1.228744   0.020309   1.249053 (  1.349684) <-快
=end

性能测试结论:

  • (1).ruby 3之前,fast_jsonparser非常快,但是Ruby 3中的fast_jsonparser很慢
  • (2).OJ解析本地json字符串的性能比标准库json性能稍好,但oj从文件中加载并解析json的速度很慢
  • (3).OJ将ruby对象解析为json字符串的效率比json标准库性能好

即:

dump:
Oj.dump > JSON.dump

ruby3 之前:
FastJsonparser.load > JSON.load > Oj.load_file
FastJsonparser.parse > Oj.load > JSON.parse

ruby3 之后:
JSON.load > Oj.load_file > FastJsonparser.load
Oj.load > JSON.parse > FastJsonparser.parse

multi_json

有一个名为multi_json的gem包,它提供多种json包的功能,默认采用OJ作为json的适配引擎。它支持下面几种json适配器:

  • Oj Optimized JSON by Peter Ohler
  • Yajl Yet Another JSON Library by Brian Lopez
  • JSON The default JSON gem with C-extensions (ships with Ruby 1.9+)
  • JSON Pure A Ruby variant of the JSON gem
  • NSJSONSerialization Wrapper for Apple’s NSJSONSerialization in the Cocoa Framework (MacRuby only)
  • gson.rb A Ruby wrapper for google-gson library (JRuby only)
  • JrJackson JRuby wrapper for Jackson (JRuby only)
  • OkJson A simple, vendorable JSON parser

如果oj已被require,则默认采用oj处理json,如果oj没有被require,而是require了yajl,则采用yajl处理json,依次类推。

它提供了load()和dump()方法:

load(json_str, options = {})
  options: 
    symbolize_keys: true, false
    adapter:  oj, json_gem, yajl, json_pure, ok_json

dump(object, options = {})
Ruby 相关文章推荐
Ruby处理CSV数据方法详解
Apr 18 Ruby
Ruby处理YAML和json数据
Apr 18 Ruby
Ruby序列化和持久化存储 Marshal和Pstore介绍
Apr 18 Ruby
Ruby使用Mysql2连接操作MySQL
Apr 19 Ruby
Ruby GDBM操作简介及数据存储原理
Apr 19 Ruby
安装Ruby和 Rails的详细步骤
Apr 19 Ruby
Python如何将list中的string转换为int
Jul 15 Ruby
Ruby处理CSV数据方法详解
Apr 18 #Ruby
Ruby序列化和持久化存储 Marshal和Pstore介绍
Apr 18 #Ruby
Ruby使用Mysql2连接操作MySQL
Apr 19 #Ruby
Ruby GDBM操作简介及数据存储原理
Apr 19 #Ruby
安装Ruby和 Rails的详细步骤
Python如何将list中的string转换为int
Jul 15 #Ruby
You might like
巧用php中的array_filter()函数去掉多维空值的代码分享
2012/09/07 PHP
PHP基础之运算符的使用方法
2013/04/28 PHP
解析php下载远程图片函数 可伪造来路
2013/06/25 PHP
php自定义加密与解密程序实例
2014/12/31 PHP
基于php的CMS中展示文章类实例分析
2015/06/18 PHP
PHP中static关键字以及与self关键字的区别
2015/07/01 PHP
CodeIgniter与PHP5.6的兼容问题
2015/07/16 PHP
php实现Session存储到Redis
2015/11/11 PHP
PHP单例模式是什么 php实现单例模式的方法
2016/05/14 PHP
PHP序列化操作方法分析
2016/09/28 PHP
php微信开发之百度天气预报
2016/11/18 PHP
javascript控制swfObject应用介绍
2012/11/29 Javascript
jQuery 计算iframe 窗口大小的方法
2014/05/13 Javascript
使用jQuery和Bootstrap实现多层、自适应模态窗口
2014/12/22 Javascript
纯jquery实现模仿淘宝购物车结算
2015/08/20 Javascript
深入理解jquery自定义动画animate()
2016/05/24 Javascript
微信小程序 this和that详解及简单实例
2017/02/13 Javascript
微信小程序 setData的使用方法详解
2017/04/20 Javascript
浅谈angularJS的$watch失效问题的解决方案
2017/08/11 Javascript
angularjs实现简单的购物车功能
2017/09/21 Javascript
详解JSON和JSONP劫持以及解决方法
2019/03/08 Javascript
微信小程序自定义头部导航栏(组件化)
2019/11/15 Javascript
[01:00:54]TI4正赛第二日开场
2014/07/20 DOTA
[51:53]完美世界DOTA2联赛决赛日 Inki vs LBZS 第二场 11.08
2020/11/10 DOTA
对python生成业务报表的实例详解
2019/02/03 Python
python 字符串格式化的示例
2020/09/21 Python
scrapy实践之翻页爬取的实现
2021/01/05 Python
澳大利亚礼品篮网站:Macarthur Baskets
2019/10/14 全球购物
P D PAOLA法国官网:西班牙著名的珠宝首饰品牌
2020/02/15 全球购物
致跳远运动员广播稿
2014/02/11 职场文书
岗位职责风险防控
2014/02/18 职场文书
法律进机关实施方案
2014/03/12 职场文书
经济信息系毕业生自荐信
2014/06/02 职场文书
2014年护士长工作总结
2014/11/11 职场文书
大学生求职意向书
2015/05/11 职场文书
《巨人的花园》教学反思
2016/02/19 职场文书