深入浅出 toml

创建:xiaozi · 最后修改:xiaozi 2025-02-16 13:52 ·

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.141590.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 中布尔值只允许两个字面量:truefalse(均为小写)。布尔值不需要加引号。

enabled = true
is_valid = false

上例中,enabledis_valid 分别被赋值为布尔值真和假。

日期与时间(Date/Time)

TOML 将日期和时间作为一等公民来支持,可直接使用 ISO 8601 / RFC 3339 格式的字面量表示日期和时间。根据精确程度和有无时区偏移,主要有以下几种:

  • 带时区的日期时间(Offset Date-Time):包含日期、时间和时区偏移量。例如:1979-05-27T07:32:00-08:001979-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 表格。

嵌套表格:表格可以包含子表格,从而形成嵌套结构。如果想表示表格内的子表,有两种方式:

  1. **点号表示法(链式键名):**直接使用点号 . 在键名中表示层级。例如:

    [database]
    host = "localhost"
    timeout = 30
    
    database.user.name = "admin"
    database.user.password = "secret"
    

    在上述示例中,database 是已存在的表格,我们通过 database.user.namedatabase.user.password 定义了一个嵌套的子表 user,其中包含 namepassword 两个键。这种链式写法会自动创建中间的表格。也就是说,即使没有显式写 [database.user],TOML 解析器也能根据前缀 database.user 推断出 user 子表并将其加入 database 表下。

  2. **显式声明子表:**另一种方式是直接使用方括号声明子表。例如:

    [database]
    host = "localhost"
    timeout = 30
    
    [database.user]
    name = "admin"
    password = "secret"
    

    这与上面的效果相同,都创建了一个 database 表格及其内部的 user 子表,只是语法不同。在显式声明方式中,我们用了 [database.user] 明确指出 userdatabase 的子表。

无论使用哪种方式,最终都会得到嵌套的层次结构。例如,以上关于 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 的内联表格,其中包含 xyz 三个键。这个定义等价于常规表格:

[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]],就表示数组中的一个新元素表格。第一个元素有两个字段 namesku;第二个元素有三个字段 nameskucolor。两个元素表格的键结构是一致的,其中第二个比第一个多了一个可选的 color 字段。

上述结构对应的 JSON 表示如下:

{
  "products": [
    { "name": "Hammer", "sku": 738594937 },
    { "name": "Nail", "sku": 284758393, "color": "gray" }
  ]
}

可以看到,products 在 JSON 中是一个数组,包含两个对象,正是通过 TOML 的表格数组构造而来。需要注意的是,如果在 [[products]] 声明下不跟任何键值对(即出现一个空的表格项),那么在解析时会得到一个空对象。通常我们会为每个表格数组元素提供至少一个字段。

**小结:**利用表格数组,我们可以方便地表示一组配置项,而无需手动在键名中引入索引号。这在表示列表结构时十分直观。例如上面的 products 列表,比起在键名中写 product1product2 等方式更加规范和清晰。

注释与特殊符号

在 TOML 中,一些符号具有特殊的语法作用,需要了解它们的使用规则:

  • **注释(#):**井号 # 用于引入注释。从 # 开始直到行尾的内容都会被解析器忽略(在字符串字面量内的 # 则不算注释)。注释可以独占一行,也可以放在某个键值对的尾部。例如:

    # 这是整行注释
    title = "TOML"  # 这是行尾注释
    path = "C:\#temp"  # 在字符串中的 # 不算注释
    

    上例演示了注释的用法。需要注意,注释中不能包含控制字符(制表符以外的 ASCII 0~31 以及 127)。

  • 点号(.):点号用于表示嵌套键名,可在一个键的名称中引入层次结构。例如 config.database.user 会被解析为表格 config 下的子表 database 下的键 user。使用点号可以让我们不必显式写出每级表格,从而简洁地定义嵌套结构。但要确保点号分隔的各部分都符合键的命名规则。

  • **方括号([]):**在 TOML 中方括号有两种用途:

    1. 声明表格:如 [section] 表示一个名为 section 的表格。
    2. 定义数组:如 [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

Главная - Вики-сайт
Copyright © 2011-2025 iteam. Current version is 2.143.0. UTC+08:00, 2025-05-19 21:23
浙ICP备14020137号-1 $Гость$