XML JSON 与 YAML
XML JSON 与 YAML
2023年10月8日
摘要
在项目的开发中,经常会需要存储和读取相当多的参数和配置信息。在不同的场景下,这些参数和配置会发生较大的变化。如果每次都手动录入这些信息,无疑会增加巨大的工作量。其次,项目与项目之间,也经常会需要交互非常多的信息。基于以上的场景,XML,JSON 与 YAML 三种主流的结构化标记语言讲可以为此提供极大的便利。本文将对他们进行介绍。
YAML
YAML 全名 YAML Aint Markup Language 是这三个语言中最现代化,也是对人类的阅读习惯而言可读性最佳的语言,和最适合用作配置文件的语言。
首先看一个简单的 YAML 例子:
Servers:
Home:
- Router:
HasPCIePassThrough: True
OS: OpenWrt
RAM: 2 GB
CPU Cores: 2
- NAS:
hasPCIePassThrough: False
OS: OpenMediaVault
RAM: 2 GB
CPU Cores: 2
- Server:
HasPCIePassThrough: False
OS: OpenSUSE MicroOS
RAM: 8 GB
CPU Cores: 4
Lab:
- WiFi-Lan:
HasPCIePassThrough: False
OS: Manjaro KDE
RAM: 2 GB
CPU Cores: 2
- Router:
HasPCIePassThrough: True
OS: OpenWrt
RAM: 4 GB
CPU Cores: 4
- Server:
HasPCIePassThrough: False
OS: Manjaro Architect
RAM: 8 GB
CPU Cores: 4
这个实例文件描述了一个服务器架构,可以观察发现,YAML 的基础语法十分简洁,即是完全没有接触过 YAML 语言的人也能大致明白其中描述的内容是什么含义。
语法
基本规则
在 YAML 中,有几项最基本的语法规则需要注意:
- 区分大小写
- 层级关系用缩进来表示,缩进的空格数量并不重要,只要处在相同层级的内容具有相同的缩进数量即可
- 缩进只能使用空格,不能使用 Tab
#
开头的是注释
支持的数据结构
YAML 支持三种类型的数据:
字典:无序排列的键值对的集合
在 YAML 中,最常用的数据结构是字典,其格式是 key: value
,注意,冒号后面的空格是必须的,例如:
name: PSC
born: 2000
当然,字典中的值并不一定要是字面意思的一个“值”,也可以是嵌套的结构,例如:
devices:
iPhone:
Model: iPhone 13 mini
Color: Pink
iPad:
Model: iPad Pro 2018 12.9
Color: Space Gray
Mac:
Model: MacBook Pro 14 M1 Max
Color: Space Gray
Apple Watch:
Model: Apple Watch Series 8 45mm
Color: Stainless Steel Silver
其中,顶层字典只有一个键值对,名为 devices
的键所对应的值是一个“具有 4 个键值对,存有 4 个设备信息的字典”,而这个字典中每一个键对应的值又是“包含他们型号和颜色的字典”。
在使用多级嵌套的字典时,需要注意不同层级对象的缩进。
数组:有序的列表
除了字典以外,有时我们也会需要存储并列的一系列元素,他们有序,且同属于一个层级,并列关系使得他们成为一个列表,并不需要给每个项目一个单独的键来命名,此时,YAML 的数组便可以支持这样的需求,其元素的格式是 - element
, 注意,横线后的空格是必须的。一个简单的数组的例子如下:
- XPENG
- Audi
- Porsche
- Bently
当然,数组也是可以嵌套的,并且可以和字典相互嵌套,例如:
XPENG:
- P5: Electric
- P7i: Electric
- G9: Electric
Audi:
- A6L: Fuel
- RS7: Fuel
其中,顶层字典包含两个键值对,键是品牌名,值是一个“包含各自车型参数的列表”,这个列表中的元素又都是“车辆型号和其能量形式的字典”。需要注意的是,数组同样也需要根据层级进行缩进,且数组的缩进是从横线处开始算起。
YAML 中的数组不要求所有元素类型相同。
纯量:不能再分割的数据最小单元
在 YAML 中,纯量包含以下几种:
- 字符串
- 布尔值
- 整数
- 浮点数
- Null(支持 Null, null, NULL)
- 时间
- 日期
纯量仅仅可以出现在列表的元素中或者字典的键值对中,下面是一个实例,只为展示语法:
Perfume: # String
Brand: by Kilian # String: String
Name: Playing with the Devil # String: String
Volume/mL: 50 # String: Int
Key Notes: #String
- Rum #String
- Coffee #String
- Cyanide #String
Price($): 290.00 # String: Float
Release: 2013-10-07 # String: Date
Suggest: True # String: Bool
Notation: Null # String: Null
纯量会自动识别类型,但也支持强制类型转换,语法如下:
PhoneNumber: !!str 13900000000
即可强制把后面的数转换为字符串进行储存。
引用
引用是 YAML 中的一个高级语法,可以减少大量的复制粘贴工作,用一个例子来说明:
Server:
host: &hostname pscpeng.xyz
Services:
Nextcloud:
host: *hostname
port: 1000
Halo Blog:
host: *hostname
port: 2000
其中,&
开头的 &hostname
便是设置了一个名为 hostname
的锚点,他代表着其后跟随的当前层级字段。要引用这段内容,使用 *
加上锚点名即可,在这里就是 *hostname
。
因此,以上 YAML 文件完全等效于:
Server:
host: pscpeng.xyz
Services:
Nextcloud:
host: pscpeng.xyz
port: 1000
Halo Blog:
host: pscpeng.xyz
port: 2000
其实整个操作和命名了一个变量差不多。
当然,引用也可以适用于嵌套结构,例如:
Cars:
A6L: &Audi_property
Brand: Audi
Properties:
Wheels: 4
Energy: Fuel
RS7: *Audi_property
完全等同于:
Cars:
A6L:
Brand: Audi
Properties:
Wheels: 4
Energy: Fuel
RS7:
Brand: Audi
Properties:
Wheels: 4
Energy: Fuel
本案例的锚点,不应理解为设置给多个值,而是设置给一整个字典,这一整个字典共同嵌套成了 A6L
键的值,然后将这个值设置成了锚点。
如果一个锚点包含了一个字典,那么还可以使用另一个语法将其与另一个字典合并,例如:
Audi Cars: &Audi_cars
A6L:
Brand: Audi
Properties:
Wheels: 4
Energy: Fuel
RS7:
Brand: Audi
Properties:
Wheels: 4
Energy: Fuel
XPENG Cars: &XPENG_cars
P5:
Brand: XPENG
Properties:
Wheels: 4
Energy: Electric
P7i:
Brand: XPENG
Properties:
Wheels: 4
Energy: Electric
All Cars:
<<: *Audi_cars
<<: *XPENG_cars
语法如 All Cars
中所示,其中 <<
代表合并。以上写法完全等价于:
Audi Cars:
A6L:
Brand: Audi
Properties:
Wheels: 4
Energy: Fuel
RS7:
Brand: Audi
Properties:
Wheels: 4
Energy: Fuel
XPENG Cars:
P5:
Brand: XPENG
Properties:
Wheels: 4
Energy: Electric
P7i:
Brand: XPENG
Properties:
Wheels: 4
Energy: Electric
All Cars:
A6L:
Brand: Audi
Properties:
Wheels: 4
Energy: Fuel
RS7:
Brand: Audi
Properties:
Wheels: 4
Energy: Fuel
P5:
Brand: XPENG
Properties:
Wheels: 4
Energy: Electric
P7i:
Brand: XPENG
Properties:
Wheels: 4
Energy: Electric
需要注意的是,All Cars
部份,不能写成:
All Cars:
*Audi_cars
*XPENG_cars
是因为即使这两个锚点展开并替换后看似语法是正确的,但未展开之前,这样编排写下的 *Audi_cars
与 *XPENG_cars
并不属于任何 YAML 有效语法,既不是数组也不是字典。
另外,在 YAML 中,数组并没有合并功能。
JSON
JSON 全名 JavaScript Object Notation 由于其用 {}
以及 []
来标记字典和数组,并且通过嵌套括号来区别层级,而不是通过大量的空格缩进来完成,因此文件大小会更小,也更适合网络上的流式传输,因此非常适合用来做 API 的数据传输。
JSON 的大部分都可类比 YAML,且更为简单,在上个章节中,描述服务器架构的 YAML 文件用 JSON 表示可以变成:
{
"Servers": {
"Home": [
{
"Router": {
"HasPCIePassThrough": true,
"OS": "OpenWrt",
"RAM": "2 GB",
"CPU Cores": 2
},
"NAS": {
"hasPCIePassThrough": false,
"OS": "OpenMediaVault",
"RAM": "2 GB",
"CPU Cores": 2
},
"Server": {
"HasPCIePassThrough": false,
"OS": "OpenSUSE MicroOS",
"RAM": "8 GB",
"CPU Cores": 4
}
}
],
"Lab": [
{
"WiFi-Lan": {
"HasPCIePassThrough": false,
"OS": "Manjaro KDE",
"RAM": "2 GB",
"CPU Cores": 2
},
"Router": {
"HasPCIePassThrough": true,
"OS": "OpenWrt",
"RAM": "4 GB",
"CPU Cores": 4
},
"Server": {
"HasPCIePassThrough": false,
"OS": "Manjaro Architect",
"RAM": "8 GB",
"CPU Cores": 4
}
}
]
}
}
整体上来讲,JSON 和 YAML 并没有太大区别,但由于 JSON 并不需要空格来完成层级划分,空格和空行只是为了人类的可读性,所以实际储存时可以变成以下形式:
{"Servers":{"Home":[{"Router":{"HasPCIePassThrough":true,"OS":"OpenWrt","RAM":"2 GB","CPU Cores":2},"NAS":{"hasPCIePassThrough":false,"OS":"OpenMediaVault","RAM":"2 GB","CPU Cores":2},"Server":{"HasPCIePassThrough":false,"OS":"OpenSUSE MicroOS","RAM":"8 GB","CPU Cores":4}}],"Lab":[{"WiFi-Lan":{"HasPCIePassThrough":false,"OS":"Manjaro KDE","RAM":"2 GB","CPU Cores":2},"Router":{"HasPCIePassThrough":true,"OS":"OpenWrt","RAM":"4 GB","CPU Cores":4},"Server":{"HasPCIePassThrough":false,"OS":"Manjaro Architect","RAM":"8 GB","CPU Cores":4}}]}}
文件体积明显小了很多,也更适合网络传输。
语法
基本规则
JSON 整体规则和 YAML 较为相似:
- 字典由
{}
包裹 - 数组由
[]
包裹 - 键值对和元素用
,
分割,最后一项后面的,
可有可无 - 字典的键必须是字符串
- 缩进和空行不是必须
- 不支持注释!
支持的数据结构
和 YAML 一样:
字典
JSON 中的字典使用 {}
包裹,键值对之间用 ,
分隔,键必须是字符串,例如:
{
"name": "PSC",
"age": 23
}
同样也支持嵌套。
数组
JSON 中的数组使用 []
包裹,元素之间用 ,
分隔,例如:
[
"XPENG",
"Audi",
"Porsche",
"Bently"
]
同样也支持嵌套,以及和字典的相互嵌套。
JSON 中的数组不要求所有元素的类型相同。
纯量
JSON 中的纯量类型更少,仅有:
- 字符串(必须用双引号包裹)
- 布尔值
- 整数
- 浮点数
- null(仅支持纯小写)
至此,JSON 再无更多语法规则,使用极其简单。
功能上来讲,可以把 JSON 理解为去除日期格式支持和引用功能的 YAML,语法上来讲,二者几乎可以进行一对一转换。
XML
XML 语言的全名是 eXtensible Markup Language 也就是可扩展标记语言。虽然 XML 从长相上来看与 HTML 非常相似,但 XML 更加注重于存储数据本身。
以下为同样服务器架构在 XML 文件中的例子:
<?xml version="1.0" encoding="UTF-8" ?>
<Servers>
<Home>
<Router>
<HasPCIePassThrough>true</HasPCIePassThrough>
<OS>OpenWrt</OS>
<RAM>2 GB</RAM>
<CPU_Cores>2</CPU_Cores>
</Router>
<NAS>
<hasPCIePassThrough>false</hasPCIePassThrough>
<OS>OpenMediaVault</OS>
<RAM>2 GB</RAM>
<CPU_Cores>2</CPU_Cores>
</NAS>
<Server>
<HasPCIePassThrough>false</HasPCIePassThrough>
<OS>OpenSUSE MicroOS</OS>
<RAM>8 GB</RAM>
<CPU_Cores>4</CPU_Cores>
</Server>
</Home>
<Lab>
<WiFi-Lan>
<HasPCIePassThrough>false</HasPCIePassThrough>
<OS>Manjaro KDE</OS>
<RAM>2 GB</RAM>
<CPU_Cores>2</CPU_Cores>
</WiFi-Lan>
<Router>
<HasPCIePassThrough>true</HasPCIePassThrough>
<OS>OpenWrt</OS>
<RAM>4 GB</RAM>
<CPU_Cores>4</CPU_Cores>
</Router>
<Server>
<HasPCIePassThrough>false</HasPCIePassThrough>
<OS>Manjaro Architect</OS>
<RAM>8 GB</RAM>
<CPU_Cores>4</CPU_Cores>
</Server>
</Lab>
</Servers>
与 YAML 和 JSON 相比,可以发现 XML 的可读性相当的差,语法较为冗杂,但在资源管理方面,XML 还是有其天生的优势的。
语法
基本规则
XML 但规则和前面两个语言相差较大:
- 元素和内容大致等同于键值对
- 必须有一个根元素
- 元素必须有关闭标签
- 元素内容没有类型区分
- 标签对大小写敏感
- 属性必须加引号
- 注释由
<!--
和-->
包裹 - 没有数组这一概念,但允许同名标签并列
元素
在 XML 中,一切都是基于元素的,以下为一个元素的例子:
<name>PSC</name>
其中由 <>
包裹的是标签,里面的字符串是标签名,类似于字典中的键,而中间的内容 PSC
则类似于字典中的值,只是每个元素末尾需要多加一个前缀是 /
的标签名的关闭标签而已。
XML 同样支持标签嵌套,例如:
<person>
<PSC>
<age>23</age>
<height>176cm</height>
</PSC>
</person>
需要注意的是,XML 中,必须有一个根元素,也就是说顶层元素只能有一个,以下内容是不合法的:
<XPENG>P5</XPENG>
<Audi>A6L</Audi>
因为有两个顶层标签。
属性
XML 相较于另外两个语言,多了一个属性的概念,可以在标签中添加一些值其他属性,例如:
<people>
<person name="PSC" sex="Male">
<age>23</age>
<height>176cm</height>
</person>
<person name="CSP" sex="Female">
<age>32</age>
<height>671cm</height>
</person>
</people>
可以看到,在元素开头的标签中,加入了两个属性:name
和 sex
。但是,其实这两个属性都可以被放进标签内部,变成 <name>PSC</name>
这样的格式,所以怎样编排要看开发者的实际场景。以 XML 的设计哲学来讲,资源属性都应尽量作为元素,而其他参考属性才适合放入标签,例如:
<img format="jpeg">src.jpeg</img>
需要注意的是,属性的值必须用双引号包裹。
同名标签
同名标签在一定程度上可以作为数组使用,例如:
<people>
<person>
<name>PSC</name>
<age>23</age>
<height>176cm</height>
</person>
<person>
<name>CSP</name>
<age>32</age>
<height>671cm</height>
</person>
</people>
具有两个名为 person
的标签。
如果一定要进行一定的区分,则可以加一个 id
的属性(名字随便取的,不叫 id
也行):
<people>
<person id="1">
<name>PSC</name>
<age>23</age>
<height>176cm</height>
</person>
<person id="2">
<name>CSP</name>
<age>32</age>
<height>671cm</height>
</person>
</people>
XML 的语法大致如上,很简洁,没有过多内容。
适用场景
- YAML:配置文件
- JSON:数据传输
- XML:资源描述