深入浅出 toml
TOML(Tom's Obvious, Minimal Language)是一种简洁明了的配置文件格式,由 GitHub 前 CEO Tom Preston-Werner 于 2013 年创建。它的设计宗旨是易读、语义清晰且最小化,能够无歧义地映射为哈希表,并方便解析为各种语言的数据结构。TOML 语法看起来与传统 INI 配置相似,但提供了更丰富的数据类型和层次结构,解决了 INI 只能表示单层嵌套、无法处理数组等局限。由于具备出色的可读性和简洁性,TOML 日益受到欢迎,并被许多现代项目采用(例如 Rust 包管理器 Cargo、Python 项目管理工具 Poetry 等)。
接下来,我们将按章节逐步介绍 TOML 的语法规则和结构,包括基本语法、支持的数据类型、表格和嵌套结构、注释与特殊符号,并与 JSON/YAML 进行对比。每个概念都会配有简洁的说明和示例代码,方便快速掌握。
基本语法规则与结构
键值对: TOML 文件的基本单元是键值对(key-value)。键名在等号 =
左侧,值在右侧,中间用 =
连接。键名和等号、值之间的空白不影响语义(会被忽略)。一个键值对必须写在同一行上。示例:
title = "TOML Example" # 键 title,值为字符串 "TOML Example"
如上所示,title
是键名,"TOML Example"
是字符串值。**注意:**TOML 是区分大小写的,意味着键名的大小写不同会被视为两个不同的键。
**键的命名:**TOML 键通常由字母、数字、下划线和连字符组成(不能以数字开头),这类不加引号的键称为“裸键”。如果键名包含特殊字符(如空格、符号)或想保留大小写原样,可以将键名用引号括起来,此时称为引号键。例如:
name = "Alice" # 裸键,无特殊字符
'server port' = 8080 # 引号键,包含空格和特殊字符
上例中,name
使用裸键格式,而 'server port'
使用引号键以允许空格。
整体结构:一个 TOML 文档本质上可以看作是嵌套的“表格”(table)结构。默认情况下,未明确放入表格的键值对属于隐式的顶层表格。为了组织配置,可以使用方括号声明表格(类似 INI 的节)。表格名写在方括号 []
中且独占一行。从该行以下到下一个表格声明或文件结尾的键值对,都归属于这个表格。例如:
title = "My App" # 顶层键值对,属于隐式的顶层表
[database] # 定义名为 "database" 的表格
server = "192.168.1.1"
ports = [8001, 8001, 8002]
connection_max = 5000
enabled = true
[owner] # 定义名为 "owner" 的表格
name = "Lance Uppercut"
dob = 1979-05-27T07:32:00-08:00 # 日期时间值,带时区偏移
上述示例定义了顶层的键 title
,以及两个表格 [database]
和 [owner]
。在 [database]
表下,我们设置了数据库服务器地址、端口列表、最大连接数和启用标志等配置;在 [owner]
表下,我们设置了姓名和出生日期时间(注意日期时间直接以 RFC 3339 格式编写)。通过表格,我们可以将相关的配置项组织在一起,层次分明。表格名本身也可以使用点号(.
)表示层级关系,从而创建嵌套表格(详见下文)。
支持的数据类型
TOML 支持多种常用的数据类型,包括:字符串、整数、浮点数、布尔值、日期/时间以及数组。下面我们分别介绍每种类型的表示方法和特点,并给出示例。
字符串(String)
TOML 中的字符串必须用引号括起来。字符串有四种表示形式:基本字符串、多行基本字符串、字面量字符串、多行字面量字符串。
-
基本字符串: 使用双引号
"
括起。支持典型的转义序列,如\"
(双引号)、\\
(反斜杠)、\n
(换行)、\t
(制表符)、\uXXXX
(Unicode 编码)等。不允许直接包含控制字符(如 ASCII 0~31)和未转义的引号或反斜杠。 -
多行基本字符串: 使用三重双引号
"""
包裹,可以直接书写换行符,字符串中的换行会被保留。适用于包含换行的大段文本。为了避免在源文件中出现多余的缩进空格,可以在行末使用反斜杠\
来忽略紧邻的换行符。例如:text = """这是第一行, 这是第二行, 第三行继续。"""
上面
text
的值将包含实际的换行符。首行紧跟在"""
后的换行不计入内容,其余行的换行将原样保留。 -
字面量字符串: 使用单引号
'
括起。单引号内的内容不会进行转义,因此非常适合表示文件路径、正则表达式这类包含许多反斜杠或特殊字符的文本。例如 Windows 文件路径:winPath = 'C:\Users\nodejs\templates'
在单引号字符串中,
\
没有特殊含义,因此上述路径中的反斜杠无需转义。 -
多行字面量字符串: 使用三重单引号
'''
包裹。其行为与多行基本字符串类似,不同之处在于仍然不支持转义。这对包含大量特殊符号的多行文本非常有用。例如:regex = '''regex匹配: ^\d{4}-\d{2}-\d{2}$'''
字符串示例:
# 基本字符串,带有转义字符
sentence = "She said: \"Hello\"\nNext line."
# 字面量字符串,适合文件路径等
path = 'C:\Windows\System32\Drivers\etc\hosts'
# 多行基本字符串
poem = """
玫瑰是红色的,
紫罗兰是蓝色的,
多行字符串让格式保持原样。
"""
整数(Integer)
整数是在 TOML 中表示整数值的数据类型。十进制整数直接写出数字序列(可加可不加正负号前缀)即可。例如:
int1 = 42
int2 = -17
为了提高可读性,可以在长数字中使用下划线 _
分隔,如 1_000_000
等,同一数字的每两个数字间最多允许一个下划线。下划线不能出现在数字的开头或结尾,也不能紧邻符号和数字之间。
# 使用下划线增强可读性
large_num = 1_234_567_890
除了十进制,TOML 1.0 还支持其他进制的整数字面量:
-
十六进制:以
0x
为前缀,例如0xFF
表示 255。 -
八进制:以
0o
为前缀,例如0o755
。 -
二进制:以
0b
为前缀,例如0b1101
。
示例:
hex_value = 0xDEADBEEF # 十六进制
oct_value = 0o755 # 八进制(Unix 文件权限常用表示)
bin_value = 0b1010_1010 # 二进制,使用下划线分隔
**注意:**整数不能有前导零(例如写成 045
是非法的),也不支持如 +NaN
、-Inf
这样的特殊值表示(特殊值仅适用于浮点数,见下节)。整数值的范围通常被限定为 64 位有符号长整型的范围。
浮点数(Float)
浮点数用于表示带小数点或指数的数值。TOML 的浮点数写法与多数编程语言相似,例如:
- 小数形式:
3.14159
,0.5
,-0.1
等。 - 科学计数法(指数形式):例如
5e2
表示 5×10^2,即 500;1.2e-3
表示 0.0012。
同整数一样,浮点数中也可以使用下划线分隔数字:
pi = 3.141_592_653 # 等价于 3.141592653
avogadro = 6.022e23 # 科学计数法
**特殊浮点值:**TOML 支持以下特殊浮点值表示无穷大和非数字(NaN):
- 正无穷大:写作
inf
或+inf
- 负无穷大:写作
-inf
- 非数字(NaN):写作
nan
,大小写不敏感(也可写作+nan
或-nan
,在解析时均视为 NaN)
示例:
temperature = -4.72 # 普通浮点数
mass = 6.02e23 # 科学计数法表示
positive_inf = inf # 正无穷大
not_a_number = nan # 非数字值
布尔值(Boolean)
布尔类型用于表示真或假。在 TOML 中布尔值只允许两个字面量:true
和 false
(均为小写)。布尔值不需要加引号。
enabled = true
is_valid = false
上例中,enabled
和 is_valid
分别被赋值为布尔值真和假。
日期与时间(Date/Time)
TOML 将日期和时间作为一等公民来支持,可直接使用 ISO 8601 / RFC 3339 格式的字面量表示日期和时间。根据精确程度和有无时区偏移,主要有以下几种:
-
带时区的日期时间(Offset Date-Time):包含日期、时间和时区偏移量。例如:
1979-05-27T07:32:00-08:00
或1979-05-27T07:32:00Z
(Z 表示 UTC)。这是最常用的日期时间格式,完整且有时区信息。 -
本地日期时间(Local Date-Time):只有日期和时间,没有时区。例如:
1979-05-27T07:32:00
。 -
本地日期(Local Date):只有日期,例如:
1979-05-27
。 -
本地时间(Local Time):只有时间,例如:
07:32:00
。
这些日期/时间字面量在 TOML 中解析时会直接映射为相应的日期时间对象,而不是字符串。例如:
dob = 1979-05-27T07:32:00-08:00 # Date-Time(含时区)
meeting_local = 2025-05-17T15:30:00 # Local Date-Time
today = 2025-05-17 # Local Date
alarm = 07:30:00 # Local Time
上例展示了各种日期时间类型的写法。需要注意的是,这些日期和时间必须符合严格的 ISO 8601 格式要求,否则将视为解析错误。
数组(Array)
数组使用方括号 []
包裹,一组同类型元素的列表。数组中的元素以逗号分隔,逗号两侧的空格和换行都会被忽略,所以可以将一个数组写成单行或多行。**重要:**数组内的所有元素类型必须相同,不能混合不同类型。(字符串的不同表示形式算作同一类型,即基本字符串和字面量字符串都属于字符串类型。)
示例:
# 数字数组
primes = [2, 3, 5, 7, 11]
# 字符串数组(分行书写)
fruits = [
"Apple",
"Banana",
"Cherry",
]
# 布尔值数组
flags = [ true, false, false, true ]
# 嵌套数组(数组的元素也可以是数组,只要所有元素类型一致)
matrix = [ [1, 2, 3], [4, 5, 6] ]
上面展示了几种数组的定义方式。可以看到,数组可以横跨多行书写,只要最后用 ]
闭合即可。另外,数组中允许最后一个元素后面保留一个尾逗号(trailing comma)——例如上例中的 fruits
列表,"Cherry",
后面保留一个逗号也是被允许的。这对在版本控制中逐行比较修改非常有帮助。
表格、嵌套结构与表格数组
在 TOML 中,“表格”(Table)相当于键值对的集合(类似字典或对象),用于组织和分组配置项。表格可以嵌套,从而形成层次化的数据结构。除了单个表格之外,TOML 还支持表格数组(Array of Tables),方便表示多个相同结构的配置项列表。本节将分别介绍表格的定义、嵌套表格的表示方法,以及表格数组的用法。
表格的定义
**声明表格:**使用方括号 []
声明一个新的表格。表格名写在方括号内并独占一行。例如:
[server]
ip = "192.168.0.1"
port = 8080
上例创建了名为 server
的表格。在此表格下,我们可以添加任意多个键值对(如 IP 和端口)。所有直到下一个表格声明或文件结尾之前出现的键值对都归入 server
表格。
嵌套表格:表格可以包含子表格,从而形成嵌套结构。如果想表示表格内的子表,有两种方式:
-
**点号表示法(链式键名):**直接使用点号
.
在键名中表示层级。例如:[database] host = "localhost" timeout = 30 database.user.name = "admin" database.user.password = "secret"
在上述示例中,
database
是已存在的表格,我们通过database.user.name
和database.user.password
定义了一个嵌套的子表user
,其中包含name
和password
两个键。这种链式写法会自动创建中间的表格。也就是说,即使没有显式写[database.user]
,TOML 解析器也能根据前缀database.user
推断出user
子表并将其加入database
表下。 -
**显式声明子表:**另一种方式是直接使用方括号声明子表。例如:
[database] host = "localhost" timeout = 30 [database.user] name = "admin" password = "secret"
这与上面的效果相同,都创建了一个
database
表格及其内部的user
子表,只是语法不同。在显式声明方式中,我们用了[database.user]
明确指出user
是database
的子表。
无论使用哪种方式,最终都会得到嵌套的层次结构。例如,以上关于 database
的配置相当于以下的 JSON 结构:
{
"database": {
"host": "localhost",
"timeout": 30,
"user": {
"name": "admin",
"password": "secret"
}
}
}
可以看到,通过 TOML 的嵌套表格,我们很容易表达嵌套的字典/对象结构,而无需像 JSON 那样使用大量的花括号,也不需要像 YAML 那样严格对齐缩进。
**键名继承:**当使用显式表格声明嵌套结构时,不需要也不应该重复声明父级表格。TOML 解析器会根据声明的子表自动推导父表存在,因此以下写法是非法的,或者说没有必要:
# 不需要显式声明中间表
# [config] <- 无需
# [config.database] <- 无需
[config.database.user] # 可以直接声明到最终子表
role = "readonly"
上例中,用 [config.database.user]
就可以直接定义三层嵌套的表,而无需先后声明 [config]
和 [config.database]
,它们会被隐式创建。
**表格内键名:**表格内部定义键值对时,键的语法规则与顶层键相同。可以使用裸键或引号键。如果键名中含有特殊字符或空格,需用引号括起来。例如:
[example]
normal_key = "value"
"special key" = "value"
如上,example
表下定义了普通键 normal_key
和带空格的键 "special key"
,后者必须用引号括起以构成有效键名。
内联表
除了上述换行形式的表格定义,TOML 还提供了一种内联表语法,使用花括号 {}
在单行内定义表格。内联表适合表示很小的表格结构,以避免占用过多行。在内联表的大括号中,可以包含零个或多个键值对,键值对之间用逗号分隔,其语法与常规表格中的键值对完全相同。例如:
point = { x = 1, y = 2, z = 3 }
上例定义了一个名为 point
的内联表格,其中包含 x
、y
、z
三个键。这个定义等价于常规表格:
[point]
x = 1
y = 2
z = 3
内联表注意事项:内联表必须在同一行内定义完毕,花括号中的内容不能出现换行(除非换行是值的一部分,但强烈不建议这样使用)。同时,内联表中最后一个键值对后不能加逗号,例如 { a = 1, b = 2, }
是不被允许的。如果发现某个表非常复杂以至于单行书写困难,那就意味着应该使用标准的多行表格定义而非内联表。
表格数组(Array of Tables)
表格数组用于表示同构的表格项列表,也就是有相同结构的一组表格。典型场景如配置多个服务器、多个用户等。表格数组的语法是在表格名的方括号外再套一层方括号,即用 [[...]]
来声明。每出现一次双层方括号的表格声明,就会向该数组添加一个新的表格元素。
例如,假设我们要配置多个产品信息,可以这样:
[[products]]
name = "Hammer"
sku = 738594937
[[products]]
name = "Nail"
sku = 284758393
color = "gray"
在上述 TOML 中,我们使用 [[products]]
声明了一个表格数组,数组名称为 products
。每出现一次 [[products]]
,就表示数组中的一个新元素表格。第一个元素有两个字段 name
和 sku
;第二个元素有三个字段 name
、sku
和 color
。两个元素表格的键结构是一致的,其中第二个比第一个多了一个可选的 color
字段。
上述结构对应的 JSON 表示如下:
{
"products": [
{ "name": "Hammer", "sku": 738594937 },
{ "name": "Nail", "sku": 284758393, "color": "gray" }
]
}
可以看到,products
在 JSON 中是一个数组,包含两个对象,正是通过 TOML 的表格数组构造而来。需要注意的是,如果在 [[products]]
声明下不跟任何键值对(即出现一个空的表格项),那么在解析时会得到一个空对象。通常我们会为每个表格数组元素提供至少一个字段。
**小结:**利用表格数组,我们可以方便地表示一组配置项,而无需手动在键名中引入索引号。这在表示列表结构时十分直观。例如上面的 products
列表,比起在键名中写 product1
、product2
等方式更加规范和清晰。
注释与特殊符号
在 TOML 中,一些符号具有特殊的语法作用,需要了解它们的使用规则:
-
**注释(#):**井号
#
用于引入注释。从#
开始直到行尾的内容都会被解析器忽略(在字符串字面量内的#
则不算注释)。注释可以独占一行,也可以放在某个键值对的尾部。例如:# 这是整行注释 title = "TOML" # 这是行尾注释 path = "C:\#temp" # 在字符串中的 # 不算注释
上例演示了注释的用法。需要注意,注释中不能包含控制字符(制表符以外的 ASCII 0~31 以及 127)。
-
点号(.):点号用于表示嵌套键名,可在一个键的名称中引入层次结构。例如
config.database.user
会被解析为表格config
下的子表database
下的键user
。使用点号可以让我们不必显式写出每级表格,从而简洁地定义嵌套结构。但要确保点号分隔的各部分都符合键的命名规则。 -
**方括号([]):**在 TOML 中方括号有两种用途:
- 声明表格:如
[section]
表示一个名为section
的表格。 - 定义数组:如
[1, 2, 3]
表示一个数组。另外,双重方括号[[section]]
用于声明表格数组(见前文)。要避免混淆,根据上下文就能判断:方括号若出现在行首并包含标识符,则是在声明表格;若出现在等号右边且包含逗号分隔的值,则是在定义数组。
- 声明表格:如
-
花括号({}):花括号用于定义内联表。例如
{ x = 1, y = 2 }
表示一个包含两个键值对的表格。内联表必须在一行内完成定义,花括号内的格式与常规表格一致。同样地,内联表中的键如果包含特殊字符也需要加引号,值如果是字符串也需要加引号。 -
引号("" 和 ''):引号用于定义字符串和包含特殊字符的键名。双引号 `` 用于基本字符串,可以使用反斜杠引入转义字符;单引号
''
用于字面量字符串,不做转义处理。此外,任何包含空格或不符合裸键规则的键名都必须放在引号内。例如:'user name' = "Tom"
合法,而不加引号的user name = "Tom"
则无法解析。 -
**特殊数字符号:**对于数字类型,有几个特殊符号值得注意:
- **下划线
_
:**可以用于分隔数字提高可读性,但不能出现在数字开头或结尾,且两侧必须紧邻数字。如file_size = 1_048_576
。 - **正负号
+
/-
:**可放在数字前表示正负,+
号通常省略不用。需要注意的是,true
/false
、日期时间等不能加+
号。 - **小数点
.
:**用于浮点数的整数部分和小数部分的分隔,例如3.14
。 - **指数符
e
/E
:**用于表示科学计数法,如5e3
等价于 5000。
- **下划线
通过以上特殊符号的配合,TOML 既保持了简洁易读,又具备了足够的表达能力来表示复杂的数据结构。熟悉这些符号的作用有助于正确书写和解读 TOML 配置。
TOML 与 JSON/YAML 的比较
最后,我们将 TOML 与另外两种常见配置/数据格式 JSON、YAML 做一个简要对比,总结它们在语法和特性上的异同:
比较项目 | TOML | JSON | YAML |
---|---|---|---|
注释支持 | 支持使用 # 添加单行注释。 |
不支持注释(标准 JSON 中不能出现注释)。 | 支持使用 # 添加单行注释。 |
数据类型 | 支持字符串、整数、浮点数、布尔值、日期时间、数组和表格等丰富类型。日期时间为一等公民,可直接表示。 | 支持字符串、数字(不区分整数/浮点)、布尔值、null、对象(键值对)和数组。不原生支持日期等,需要用字符串表示。 | 支持字符串、整数、浮点数、布尔值、null,以及日期时间等(如果符合特定格式会当作日期解析)。 |
结构表示 | 通过表格([ ] )和嵌套键名表示层次结构,语法直观类似 INI,但支持多层嵌套。数组用 [] 表示列表,表格数组用 [[ ]] 表示。 |
通过花括号 {} 和方括号 [] 定义对象和数组,嵌套结构用多层花括号表示,需要严格匹配符号。所有键和字符串必须加引号。 |
通过缩进和冒号来表示层级关系,列表项用 - 开头。语法上不需要大量符号,结构一目了然,但对空白缩进高度敏感。 |
可读易写性 | 以人为可读为优先设计,格式简洁明了,配置项清晰分组,避免了过多的符号干扰。特别适合手工编辑和检查。 | 格式严谨但冗长,所有对象括号和引号增加了阅读难度。缺乏多行字符串表示,手工编辑和修改不便。常用于机器交换,不便于直接用作手写配置。 | 可读性较好,常被用于编写配置文件。然而 YAML 语法灵活度极高,隐藏了很多复杂特性,容易因为缩进或特殊符号(如缩写、锚点等)出错。虽然适合表示复杂数据,但初学者容易踩坑。 |
说明:上表中,JSON 指标准 JSON 格式;YAML 指 YAML 1.2 规范。
总结:TOML 相比 JSON 和 YAML,更强调语义明显的配置表达,语法元素少且规则统一,兼具易读性和明确性。JSON 偏重数据交换,格式严格机器友好但不便人工编写;YAML 灵活强大但复杂度高,对格式要求严格。对于需要人来读写的配置文件,TOML 提供了一种平衡——既有明确的结构和类型支持,又保持了接近自然语言的简洁格式,因而被视作当前配置文件领域的有力竞争者之一。希望通过本教程的讲解和示例,您已经对 TOML 的语法结构和使用方法有了清晰的认识,并能在实际项目中应用这一简洁而强大的配置语言。
浏览 17 次 · 下载PDF