彭某的技术折腾笔记

彭某的技术折腾笔记

XML JSON 与 YAML

2023-10-08

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 中,有几项最基本的语法规则需要注意:

  1. 区分大小写
  2. 层级关系用缩进来表示,缩进的空格数量并不重要,只要处在相同层级的内容具有相同的缩进数量即可
  3. 缩进只能使用空格,不能使用 Tab
  4. # 开头的是注释

支持的数据结构

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 较为相似:

  1. 字典由 {} 包裹
  2. 数组由 [] 包裹
  3. 键值对和元素用 , 分割,最后一项后面的 , 可有可无
  4. 字典的键必须是字符串
  5. 缩进和空行不是必须
  6. 不支持注释!

支持的数据结构

和 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 但规则和前面两个语言相差较大:

  1. 元素和内容大致等同于键值对
  2. 必须有一个根元素
  3. 元素必须有关闭标签
  4. 元素内容没有类型区分
  5. 标签对大小写敏感
  6. 属性必须加引号
  7. 注释由 <!----> 包裹
  8. 没有数组这一概念,但允许同名标签并列

元素

在 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>

可以看到,在元素开头的标签中,加入了两个属性:namesex。但是,其实这两个属性都可以被放进标签内部,变成 <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:资源描述
  • 0