您现在的位置是:网站首页> 编程资料编程资料

Ruby解析处理YAML和json格式数据_ruby专题_

2023-05-26 431人已围观

简介 Ruby解析处理YAML和json格式数据_ruby专题_

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}" "\"#\""

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}" "\"#\""

此外,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 "#"

定义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 ------------------------
                
                

-六神源码网