Ruby处理YAML和json

回到Ruby系列文章


Ruby处理YAML

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

例如:

1
2
3
4
5
6
7
8
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

得到:

1
2
3
4
5
"--- 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相同的功能,它还可以写入文件。

1
2
3
4
users = [{name: 'Bob', permissions: ['Read']},
{name: 'Alice', permissions:['Read', 'Write']}]

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

查看文件:

1
2
3
4
5
6
7
8
---
- :name: Bob #=> 注意,保留了hash源数据中的符号
:permissions:
- Read
- :name: Alice
:permissions:
- Read
- Write

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

得到:

1
2
3
4
5
6
7
8
9
10
{"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

1
pp YAML.load(DATA, symbolize_names: true)

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

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

Ruby处理Json数据

转为json格式字符串

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

1
2
3
4
5
6
7
8
9
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]))

得到:

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

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

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

得到:

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

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

1
2
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数据类型,使用JSON.parse(),

1
2
3
4
5
6
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():

1
2
3
4
5
6
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字符串中恢复为一个对象。

例如,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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数据:

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

定义as_jsonfrmo_json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

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

1
2
3
4
5
data = Person.new("junmajinlong", "1999-10-11").as_json
p data

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

如果是读写json文件,可:

1
2
3
4
5
6
7
8
9
10
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
文章作者: 骏马金龙
文章链接: http://www.junmajinlong.com/ruby/ruby_yaml_json/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 骏马金龙
打赏我