YAML(YAML Ain’t a Markup Language)是专门用来写配置文件的语言,简洁强大,相比于 JSON 和 XML,更加便于开发人员读写。
YAML 配置文件后缀为.yml 或 .yaml。
1.语法
YAML 的基本语法规则如下:
- 数据结构采用键值对的形式 key: value。
- 键冒号后面要加空格(一般为 1 个空格)。
- 字母大小写敏感。
- 使用缩进表示层级关系。
- 缩进只允许使用空格,不允许使用 Tab 键。
- 缩进空格数可以任意,只要相同层级的元素左侧对齐即可。
- 字符串值一般不使用引号,必要时可使用。使用双引号表示字符串时,会转义字符串中的特殊字符(例如n)。使用单引号时不会转义字符串中的特殊字符。
- 数组中的每个元素单独一行,并以 - 开头。或使用方括号,元素用逗号隔开。注意短横杆和逗号后面都要有空格。
- 对象中的每个成员单独一行,使用键值对形式。或者使用大括号并用逗号分开。
- 文档以三个连字符
---
表示开始,以三个点号...
表示结束,二者都是可选的。 - 文档前面可能会有指令,在这种情况下,需要使用
---
来表示指令的结束。指令是一个%后跟一个标识符和一些形参。 - 目前只有两个指令:
%YAML
指定文档的 YAML 版本,%TAG
用于 tag 简写。二者都很少使用。 #
表示注释,从这个字符一直到行尾,都会被解析器忽略。
2.历史版本
版本 | 发布日期 |
---|---|
YAML 1.0 | 29 January 2004 |
YAML 1.1 | 18 January 2005 |
YAML 1.2.0 | 21 July 2009 |
YAML 1.2.1 | 1 October 2009 |
YAML 1.2.2 | 1 October 2021 |
3.数据结构
YAML 支持的数据结构有三种:
- 对象:键值对的集合,又称为映射(mapping)、散列(hashes)、字典(dictionary)。
- 数组:一组按次序排列的值,又称为序列(sequence)、列表(list)。
- 标量:单个不可再分的值
下面分别介绍这三种数据结构。
对象
对象的一组键值对,使用冒号结构表示。
代码语言:javascript复制name: Steve
YAML 也允许另一种写法,将所有键值对写成一个行内对象。
代码语言:javascript复制who: { name: Steve, age: 18 }
当然,如果对象元素太多一行放不下,那么可以换行。
代码语言:javascript复制who:
name: Steve
age: 18
数组
一组以连字符开头的行,构成一个数组。注意,连字符后需添加空格。
代码语言:javascript复制animals:
- Cat
- Dog
- Goldfish
连字符前可以没有缩进,也就是说下面这种写法也是 OK 的,但是还是建议缩进,因为更加易读。
代码语言:javascript复制animals:
- Cat
- Dog
- Goldfish
数组也可以采用行内表示法。
代码语言:javascript复制animal: [Cat,Dog,Goldfish]
如果数组元素是一个数组,则可以在连字符下面再缩进输入一个数组。
代码语言:javascript复制animals:
-
- Cat
- Dog
-
- Fish
- Goldfish
如果是行内表示,则为:
代码语言:javascript复制animals: [[Cat,Dog],[Fish,Goldfish]]
如果数组元素是一个对象,可以写作:
代码语言:javascript复制animals:
- species: dog
name: foo
- species: cat
name: bar
对应的 JSON 为:
代码语言:javascript复制{
"animals": [
{
"species": "dog",
"name": "foo"
},
{
"species": "cat",
"name": "bar"
}
]
}
复合结构
对象和数组可以结合使用,形成复合结构。
代码语言:javascript复制languages:
- Ruby
- Perl
- Python
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Python: python.org
Perl: use.perl.org
对应的 JSON 表示如下:
代码语言:javascript复制{
"languages": [
"Ruby",
"Perl",
"Python"
],
"websites": {
"YAML": "yaml.org",
"Ruby": "ruby-lang.org",
"Python": "python.org",
"Perl": "use.perl.org"
}
}
标量
标量是最基本、不可再分的值。有以下 7 种:
- 字符串
- 布尔值
- 整数
- 浮点数
- Null
- 时间
- 日期
使用一个例子来快速了解标量的基本使用:
代码语言:javascript复制boolean:
- TRUE # true、True 都可以
- FALSE # false、False 都可以
float:
- 3.14 # 数值直接以字面量的形式表示
- 6.8523015e 5 # 可以使用科学计数法
int:
- 123 # 数值直接以字面量的形式表示
- 0b1010_0111_0100_1010_1110 # 二进制表示
null:
nodeName: 'node'
parent: ~ # 使用~表示 null
string:
- hello # 字符串默认不使用引号
- "Hello world" # 使用双引号或单引号包裹含有空格或特殊字符(如冒号)的字符串
- newline
newline1 # 字符串可以拆成多行,每一换行符会被转化成一个空格
date:
- 2018-02-17 # 日期必须使用 ISO 8601 格式,即 yyyy-MM-dd
datetime:
- 2018-02-17T15:02:31 08:00 # 时间使用 ISO 8601 格式,时间和日期之间使用 T 连接, 08:00 表示时区
YAML 字符串有三种表示方式:
- 无引号
- 双引号
- 单引号
字符串默认不需要引号,但是如果字符串包含空格或特殊字符(如冒号),需要加引号。
双引号字符串允许在字符串中使用转义序列来表示特殊字符,例如 n
表示换行,t
表示制表符,以及 "
表示双引号。
单引号字符串被视为纯粹的字面字符串,不支持转义序列。
如果字符串含有单引号,可以使用双引号包裹,反之亦然。
4.引用
锚点 & 和别名 *,可以用来完成引用。
代码语言:javascript复制defaults: &defaults
adapter: postgres
host: localhost
development:
database: myapp_development
<<: *defaults
等同于下面的配置。
代码语言:javascript复制defaults:
adapter: postgres
host: localhost
development:
database: myapp_development
adapter: postgres
host: localhost
& 用来建立锚点(defaults),<< 表示合并到当前数据,* 用来引用锚点。
5.文本块
如果想引入多行的文本块,可以使用 |
,|
,|-
,>
,>
,>-
。
|
当内容换行时,保留换行符。
如果最后一行有多个换行符,只保留一个换行符。
代码语言:javascript复制linefeed1: |
some
text
linefeed2: |
some
text
linefeed3: |
some
text
对应的 JSON 为:
代码语言:javascript复制{
"linefeed1": "somentextn",
"linefeed2": "somentextn",
"linefeed3": "somenntextn"
}
|
当内容换行时,保留换行符。
与 | 的区别是,如果最后一行有多个换行符,则保留实际数目。
代码语言:javascript复制linefeed1: |
some
text
linefeed2: |
some
text
linefeed3: |
some
text
对应的 JSON 为:
代码语言:javascript复制{
"linefeed1": "somentextn",
"linefeed2": "somentextnn",
"linefeed3": "somenntextn"
}
|-
当内容换行时,保留换行符,但最后的换行符不保留。
代码语言:javascript复制linefeed1: |-
some
text
linefeed2: |-
some
text
linefeed3: |-
some
text
对应的 JSON 为:
代码语言:javascript复制{
"linefeed1": "somentext",
"linefeed2": "somentext",
"linefeed3": "somenntext"
}
>
当内容换行时,替换为空格,但保留最后一行的换行符。
如果最后一行有多个换行符,只保留一个换行符。
代码语言:javascript复制linefeed1: >
some
text
linefeed2: >
some
text
linefeed3: >
some
text
对应的 JSON 为:
代码语言:javascript复制{
"linefeed1": "some textn",
"linefeed2": "some textn",
"linefeed3": "somentextn"
}
>
当内容换行时,替换为空格,但保留最后一行的换行符。
与 > 的区别是,如果最后一行有多个换行符,则保留实际数目。
代码语言:javascript复制linefeed1: >
some
text
linefeed2: >
some
text
linefeed3: >
some
text
对应的 JSON 为:
代码语言:javascript复制{
"linefeed1": "some textn",
"linefeed2": "some textnn",
"linefeed3": "somentextn"
}
>-
(缺省行为)
当内容换行时,替换为空格,不保留最后一行的换行符。
代码语言:javascript复制linefeed1: >-
some
text
linefeed2: >-
some
text
linefeed3: >-
some
text
对应的 JSON 为:
代码语言:javascript复制{
"linefeed1": "some text",
"linefeed2": "some text",
"linefeed3": "somentext"
}
注意:以上 6 个特殊字符,|-
和 >-
用得最多。
6.显示指定类型
有时需要显示指定某些值的类型,可以使用 !(感叹号)显式指定类型。
! 单叹号通常是自定义类型,!! 双叹号是内置类型。
代码语言:javascript复制# !!str 指定为字符串
string.value: !!str HelloWorld!
# !!timestamp 指定为日期时间类型
datetime.value: !!timestamp 2021-04-13T02:31:00 08:00
内置的类型如下:
代码语言:javascript复制!!int:整数类型
!!float:浮点类型
!!bool:布尔类型
!!str:字符串类型
!!binary:二进制类型
!!timestamp:日期时间类型
!!null:空值
!!set:集合类型
!!omap,!!pairs:键值列表或对象列表
!!seq:序列
!!map:散列表类型
7.单文件多文档
一个 yaml 文件可以包含多个 yaml 文档,使用三个连字符---
分隔。
a: 10
b:
- 1
- 2
- 3
---
a: 20
b:
- 4
- 5
这种情况在 K8S 和 SpringBoot 中非常常见。
比如 SpringBoot 在一个 application.yml 文件中,通过 — 分隔多个不同配置,根据 spring.profiles.active 的值来决定启用哪个配置。
代码语言:javascript复制# 公共配置
spring:
profiles:
active: prod #使用名为 prod 的配置,这里可以切换成 dev。
datasource:
url: jdbc:mysql://localhost:3306/test_db?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
password: 123456
username: root
---
# 开发环境配置
spring:
profiles: dev #profiles属性代表配置的名称。
server:
port: 8080
---
# 生产环境配置
spring:
profiles: prod
server:
port: 80
8.解析
下面以 YAML 表示一个简单的后台服务配置:
代码语言:javascript复制name: UserProfileServer
maxconns: 1000
queuecap: 10000
queuetimeout: 300
loginfo:
loglevel: ERROR
logsize: 10M
lognum: 10
logpath: /usr/local/app/log
下面以 Go 语言为例,解析上面的 YAML 配置。
因为 Go 官方并没有提供解析 YAML 的标准库,所以这里基于第三方开源库 go-yaml 来完成对 YAML 文件的解析。
第一步,将 YAML 配置文件的内容在 Convert YAML to Go struct 转换为 Go struct。
代码语言:javascript复制type Server struct {
Name string `yaml:"name"`
Maxconns int `yaml:"maxconns"`
Queuecap int `yaml:"queuecap"`
Queuetimeout int `yaml:"queuetimeout"`
Loginfo struct {
Loglevel string `yaml:"loglevel"`
Logsize string `yaml:"logsize"`
Lognum int `yaml:"lognum"`
Logpath string `yaml:"logpath"`
} `yaml:"loginfo"`
}
第二步,利用第三方开源库 go-yaml 来完成对 YAML 文件的解析。
代码语言:javascript复制package main
import(
"io/ioutil"
"fmt"
"os"
yaml "gopkg.in/yaml.v3"
)
func main() {
file, err := os.Open("server.yaml")
if err != nil {
fmt.Printf("error:%vn", err)
return
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
fmt.Printf("error:%vn", err)
return
}
v := Server{}
err = yaml.Unmarshal(data, &v)
if err != nil {
fmt.Printf("error:%vn", err)
return
}
fmt.Printf("% vn", v)
}
运行输出:
代码语言:javascript复制{Name:UserProfileServer Maxconns:1000 Queuecap:10000 Queuetimeout:300 Loginfo:{Loglevel:ERROR Logsize:10M Lognum:10 Logpath:/usr/local/app/log}}
9.完整示例
代码语言:javascript复制%YAML 1.2
---
receipt: Oz-Ware Purchase Invoice
date: 2012-08-06
customer:
given: Dorothy
family: Gale
items:
- part_no: A4786
descrip: Water Bucket (Filled)
price: 1.47
quantity: 4
- part_no: E1628
descrip: High Heeled "Ruby" Slippers
size: 8
price: 133.7
quantity: 1
bill-to: &id001
street: |
123 Tornado Alley
Suite 16
city: East Centerville
state: KS
ship-to: *id001
specialDelivery: >
Follow the Yellow Brick
Road to the Emerald City.
Pay no attention to the
man behind the curtain.
...
注意在 YAML 中,字符串不一定要用双引号标示。另外,在缩进中空白字符的数目并不是非常重要,只要相同层次结构的元素左侧对齐就可以了(不过不能使用 TAB 字符)。
%YAML 1.2 表示版本。
这个文件的顶层由七个键值组成:其中一个键值"items",是两个元素构成的数组(或称清单),这数组中的两个元素同时也是包含了四个键值的散列表。
文件中重复的部分用这个方法处理:使用锚点(&)和引用(*)标签将"bill-to"散列表的内容复制到"ship-to"散列表。也可以在文件中加入选择性的空行,以增加可读性。
在一个文件中,可同时包含多个文件,并用---
分隔。选择性的符号...
可以用来表示文件结尾(在流通信中,这非常有用,可以在不关闭流的情况下,发送结束信号)。
参考文献
The Official YAML Web Site Convert YAML to JSON go-yaml - Github Convert YAML to Go struct YAML 详解与实战-CSDN YAML - 维基百科,自由的百科全书 Brief YAML reference — Camel 0.1.2 documentation