TOML 配置语言完全指南:设计目标、语法全解与多格式对比
2026/9/20 10:53:07 网站建设 项目流程

TOML 配置语言完全指南:设计目标、语法全解与多格式对比

【免费下载链接】tomlTom's Obvious, Minimal Language项目地址: https://gitcode.com/gh_mirrors/to/toml

TOML(Tom's Obvious, Minimal Language)是一款以"语义显而易见"为第一原则的配置文件格式,由 Tom Preston-Werner、Pradyun Gedam 等人共同设计。本文以本仓库中的 README.md 为骨架,结合完整的 规范文档 与 ABNF 文法,系统讲解 TOML 的设计动机、全部内置数据类型与语法细节、与 JSON/YAML/INI 的定位差异,以及规范本身在仓库中的演进与发布方式,帮助你写出一眼可读、零歧义、可被任意主流语言直接解析的配置文件。

设计目标:为什么需要 TOML

TOML 的定位在 README.md 的 Objectives 一节中表达得非常明确,它可以概括为三条核心原则:

  • 最小化:TOML 是一种极简的配置文件格式,只提供完成配置任务所必需的语言要素,不追求序列化任意数据结构的完备性;
  • 语义明显:格式的读写不依赖上下文推断,任何一行配置的目的都清晰可见,降低人类阅读与维护成本;
  • 无歧义映射:TOML 被设计为可以无歧义地映射到一张哈希表(hash table),且应当能被多种语言轻松解析成对应的数据结构。

这三条原则共同决定了 TOML 的两个重要外观特征:文件顶层永远是一张哈希表(不存在顶层数组、顶层裸浮点这类结构),以及语法规则严格而收敛(例如键的拼写形式受限、非法写法在解析期即报错)。后者也意味着 TOML 的解析器实现相对简单——这也是规范允许自由采用各语言实现的前提。

快速上手:一个完整的 TOML 文档

README.md 给出了一个覆盖绝大多数语法的官方示例,先把它完整保留下来,作为后文逐一拆解的素材:

# This is a TOML document. title = "TOML Example" [owner] name = "Tom Preston-Werner" dob = 1979-05-27T07:32:00-08:00 # First class dates [database] server = "192.168.1.1" ports = [ 8000, 8001, 8002 ] connection_max = 5000 enabled = true [servers] # Indentation (tabs and/or spaces) is allowed but not required [servers.alpha] ip = "10.0.0.1" dc = "eqdc10" [servers.beta] ip = "10.0.0.2" dc = "eqdc10" [clients] data = [ ["gamma", "delta"], [1, 2] ] # Line breaks are OK when inside arrays hosts = [ "alpha", "omega" ]

这个示例在短短的 30 行里展示了 TOML 的几乎所有核心概念:

示例片段展示的语法点
title = "TOML Example"根表中的键值对、基础字符串
[owner][database][servers][clients]表(Table)头,[a.b.c]形式的点号路径
dob = 1979-05-27T07:32:00-08:00带时区偏移的日期时间(Offset Date-Time)
ports = [ 8000, 8001, 8002 ]同构数组
connection_max = 5000整数(支持_千分位分隔)
enabled = true布尔值
[servers.alpha]与缩进嵌套表;缩进被视为空白、可加可不加
data = [ ["gamma", "delta"], [1, 2] ]嵌套数组、异构元素
hosts = [ "alpha", "omega" ]跨行数组(允许在数组内换行)
各种#注释行内注释与整行注释

将该示例交给任何符合规范的 TOML 解析器,得到的结构等价于如下 JSON:

{ "title": "TOML Example", "owner": { "name": "Tom Preston-Werner", "dob": "1979-05-27T07:32:00-08:00" }, "database": { "server": "192.168.1.1", "ports": [8000, 8001, 8002], "connection_max": 5000, "enabled": true }, "servers": { "alpha": { "ip": "10.0.0.1", "dc": "eqdc10" }, "beta": { "ip": "10.0.0.2", "dc": "eqdc10" } }, "clients": { "data": [["gamma", "delta"], [1, 2]], "hosts": ["alpha", "omega"] } }

注意到[servers.alpha][servers.beta]自动把servers变成了一张子表——这正是"点号路径表头"的语义:TOML 会替你补齐所有中间层级的 super-table。

TOML 与 JSON、YAML、INI:定位与取舍

README.md 用专门的 Comparison 一节说明了 TOML 与其他常见格式的关系,这是理解 TOML 设计哲学的关键。

与 JSON、YAML 的共性

  • TOML 与 JSON:两者都足够简单,使用的数据类型普遍存在,因而对机器来说都"好写、好解析";
  • TOML 与 YAML:两者都强调人类可读性,例如都支持注释,便于读者理解每一行的用途。

TOML 的差异点:取两者之长

TOML 的特殊之处在于组合了上述优点

  • 像 JSON 一样保持语法精简与类型收敛,但支持注释(JSON 不支持);
  • 像 YAML 一样强调可读性,但避免了 YAML 语义上的复杂性,从而保持简单。

TOML 的边界:它不是通用序列化格式

README 特别提醒了几个容易被误解的边界:

  1. 解析容易,但设计用途只是配置:TOML 并不打算用于序列化任意数据结构;
  2. 顶层恒为哈希表:文件顶层不允许出现裸数组或裸浮点,因此某些数据(如一个纯数组)无法被 TOML 直接序列化;
  3. 没有流式边界标识:TOML 文件没有标准化的起始/结束标记,通过流传输时需要由应用层自行协商边界。

与 INI 的对比

INI 文件因语法相似且同为配置文件,经常被拿来与 TOML 比较。但 INI没有标准化规范,且不能优雅地处理超过一两层的嵌套,这正是 TOML 用[a.b.c]表路径、内联表、表数组等机制解决的问题。

语法基础:注释、键值对与键

规范文档 对 README 示例中出现的每一项语法都给出了精确定义,以下按主题深入。

注释(Comment)

#符号标记该行其余部分为注释,但在字符串内部不生效:

# This is a full-line comment key = "value" # This is a comment at the end of a line another = "# This is not a comment"
  • 注释中不允许出现除 tab(U+0009)以外的控制字符(U+0000 至 U+0008、U+000A 至 U+001F、U+007F);
  • 注释只服务于人类读者,解析器绝不能依据注释的存在或内容修改键与值

键值对(Key/Value Pair)

键值对是 TOML 文档的基本单元:键在等号左侧,值在右侧,等号两侧空白被忽略,且键、等号、值必须位于同一行(部分多行值除外):

key = "value"

未指定值是非法的,key = # INVALID这种写法会直接报错;一个键值对之后必须有换行或文件结束,first = "Tom" last = "Preston-Werner"这样的同行双键值对也是非法的。

值只能是规范规定的十种类型之一:字符串、整数、浮点、布尔、Offset Date-Time、Local Date-Time、Local Date、Local Time、数组、内联表。

键(Keys):裸键、引号键与点号键

键有三种写法,规范对每种都有严格约束:

裸键(bare keys)只能包含 ASCII 字母、数字、下划线和连字符(A-Za-z0-9_-)。注意纯数字裸键如1234是合法的,但永远按字符串解释:

key = "value" bare_key = "value" bare-key = "value" 1234 = "value"

引号键(quoted keys)遵循基础字符串或字面字符串的规则,因此可以承载远更宽泛的键名(如包含空格、点号、Unicode 字符);最佳实践是"能不用就不用":

"127.0.0.1" = "value" "character encoding" = "value" "ʎǝʞ" = "value" 'key2' = "value" 'quoted "value"' = "value"

裸键必须非空;空引号键合法但被劝阻;不能用多行字符串定义键。

点号键(dotted keys)是用点连接的一串裸键或引号键,用于把相关属性归组:

name = "Orange" physical.color = "orange" physical.shape = "round" site."google.com" = true

点号两侧的空白被忽略(但规范建议不加多余空白),缩进同样被视为空白。同一键重复定义是非法的,且裸键与引号键等价——spelling = "favorite"之后再写"spelling" = "favourite"会报错。

点号键还有一个重要语义:只要某个键尚未被直接定义,就仍然可以向它及它内部的名称写入,从而隐式创建中间表:

# This makes the key "fruit" into a table. fruit.apple.smooth = true # So then you can add to the table "fruit" like so: fruit.orange = 2

但反过来,一旦fruit.apple = 1fruit.apple定义成了整数,再写fruit.apple.smooth = true就是非法的——"不能把整数变成表"。

字符串的四种形态

字符串分为基础、多行基础、字面、多行字面四种,且所有字符串只能包含 Unicode 字符

基础字符串(Basic Strings)

用双引号包裹,除必须转义的字符外可使用任意 Unicode 字符。规范内置一组紧凑转义序列:

转义含义码点
\b退格 backspaceU+0008
\t制表符 tabU+0009
\n换行 linefeedU+000A
\f换页 form feedU+000C
\r回车 carriage returnU+000D
\e转义 escapeU+001B
\"双引号U+0022
\\反斜杠U+005C
\xHHUnicode(码点 ≤ 0xFF)U+00HH
\uHHHHUnicodeU+HHHH
\UHHHHHHHHUnicodeU+HHHHHHHH

示例:

str = "I'm a string. \"You can quote me\". Name\tJos\xE9\nLocation\tSF."

\xHH\uHHHH\UHHHHHHHH必须转义为合法的 Unicode 标量值(scalar value);未列出的转义序列一律保留并应产生错误。注意TOML 字符串是 Unicode 字符序列而非字节序列,二进制数据应使用十六进制或 Base64 等字节转文本策略,而不是塞进转义码。

多行基础字符串(Multi-line Basic Strings)

用三引号包裹、允许换行;紧跟开始定界符的第一个换行会被裁剪,其余空白与换行原样保留。解析器可以把换行规范化为平台习惯的形式(Unix 下等同\n,Windows 下等同\r\n)。配合"行尾反斜杠"(line ending backslash),可以写出不引入多余空白的超长字符串:

str1 = "The quick brown fox jumps over the lazy dog." str2 = """ The quick brown \ fox jumps over \ the lazy dog.""" str3 = """\ The quick brown \ fox jumps over \ the lazy dog.\ """

以上三个字符串字节级等价。规则:当一行的最后一个非空白字符是未转义的\时,它与后面直到下一个非空白字符或结束定界符的所有空白(含换行)一并被裁剪。多行基础字符串内可以出现单个或两个连续的双引号(包括紧贴定界符内侧),但三个及以上需要转义处理。

字面字符串(Literal Strings)

用单引号包裹、完全不允许转义,所见即所得,适合 Windows 路径或正则表达式:

winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' quoted = 'Tom "Dubs" Preston-Werner' regex = '<\i\c*\s*>'

多行字面字符串(Multi-line Literal Strings)

用三单引号包裹、允许换行、仍无任何转义,开头紧跟的第一个换行被裁剪,换行规范化规则与多行基础字符串一致;字符串内最多允许连续两个单引号,三个及以上非法。

数值:整数、浮点与特殊值

整数(Integer)

支持正负号前缀(+99420-17),可用下划线增强可读性(下划线两侧必须都有数字,如1_0005_349_221);不允许前导零-0+0合法且等价于0。非负整数还支持十六进制0x、八进制0o、二进制0b前缀(十六进制不区分大小写,前缀后允许前导零,下划线不能出现在前缀与数字之间):

hex1 = 0xDEADBEEF oct1 = 0o01234567 oct2 = 0o755 # useful for Unix file permissions bin1 = 0b11010110

规范要求实现至少能无损接受并处理 64 位有符号整数(−2^63 到 2^63−1),无法无损表示时必须报错;整数大小不受限制时由实现自行决定。

浮点(Float)

浮点 = 整数部分 + 小数部分和/或指数部分(若两者都有,小数部分必须在指数之前):

# fractional flt1 = +1.0 flt2 = 3.1415 flt3 = -0.01 # exponent flt4 = 5e+22 flt5 = 1e06 flt6 = -2E-2 # both flt7 = 6.626e-34

小数点两侧必须各至少有一位数字,.77.3.e+20都是非法浮点;小数/指数部分内部也允许下划线(224_617.445_991_228)。-0.0+0.0合法且按 IEEE 754 映射。特殊值一律小写:

sf1 = inf # positive infinity sf2 = +inf # positive infinity sf3 = -inf # negative infinity sf4 = nan # actual sNaN/qNaN encoding is implementation-specific sf5 = +nan # same as `nan` sf6 = -nan # valid, encoding is implementation-specific

规范建议实现至少支持 IEEE 754 binary64 精度。

布尔与日期时间

布尔(Boolean)

只有两个全小写 token:truefalse

Offset Date-Time(带偏移日期时间)

用于无歧义地表示某一时刻,采用 RFC 3339 格式并带时区偏移:

odt1 = 1979-05-27T07:32:00Z odt2 = 1979-05-27T00:32:00-07:00 odt3 = 1979-05-27T00:32:00.5-07:00 odt4 = 1979-05-27T00:32:00.999999-07:00

日期与时间之间的分隔符T可替换为空格(RFC 3339 第 5.6 节允许);秒可以省略,省略时按:00处理:

odt5 = 1979-05-27 07:32:00Z odt6 = 1979-05-27 07:32Z odt7 = 1979-05-27 07:32-07:00

实现至少须支持毫秒精度;超出支持精度的多余小数位必须截断而不是四舍五入

Local Date-Time / Local Date / Local Time

去掉偏移的 RFC 3339 日期时间即为 Local Date-Time,它不与任何时区挂钩,也无法单独换算成时刻:

ldt1 = 1979-05-27T07:32:00 ldt2 = 1979-05-27T07:32:00.5 ldt3 = 1979-05-27T00:32:00.999999 ldt4 = 1979-05-27T07:32

只写日期是 Local Date(ld1 = 1979-05-27);只写时间是 Local Time:

lt1 = 07:32:00 lt2 = 00:32:00.5 lt3 = 00:32:00.999999 lt4 = 07:32

Local Date 表示一整天,Local Time 表示一天中的某个时刻,两者都与时区无关;时间秒同样可省略,精度规则同上。

数组、表与嵌套结构

数组(Array)

方括号包围的有序值集合,元素用逗号分隔,空白被忽略;允许混合不同类型的元素(这是与早期版本的重要差异,见 CHANGELOG.md 中 1.0.0-rc.1 的说明):

integers = [ 1, 2, 3 ] colors = [ "red", "yellow", "green" ] nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ] nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] string_array = [ "all", 'strings', """are the same""", '''type''' ] # Mixed-type arrays are allowed numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ] contributors = [ "Foo Bar <foo@example.com>", { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } ]

数组可以跨多行,允许末尾逗号(trailing comma),值、逗号、闭括号前可以随意出现换行和注释:

integers3 = [ 1, 2, # this is ok ]

表(Table)

表即哈希表/字典,由独占一行的方括号表头定义,表头之下的键值对归属该表直到下一个表头或文件结束;表内键值对不保证任何顺序

[table-1] key1 = "some string" key2 = 123 [table-2] key1 = "another string" key2 = 456

表名规则与键相同,因此可以写出[dog."tater.man"]这种含引号键的表名,对应 JSON 结构{ "dog": { "tater.man": { ... } } }。表头两侧空白被忽略,缩进也是空白;嵌套深度没有硬性上限,但规范建议实现至少支持 100 层以防资源滥用。

几个关键语义需要牢记:

  • 中间 super-table 可省略[x.y.z.w]无需先声明[x][x.y][x.y.z],之后补写[x]也合法;
  • 表不能重复定义:同一[fruit]出现两次、或在[fruit]下定义了apple = "red"后又写[fruit.apple],均非法;
  • 根表(root table):位于文档开头、第一个表头之前(或 EOF 之前),无名且不可移动;
  • 点号键与表头的互斥:用点号键创建的表不能再用[table]表头重定义;但可以用[table]表头在点号键创建的表中定义子表
[fruit] apple.color = "red" apple.taste.sweet = true # [fruit.apple] # INVALID # [fruit.apple.taste] # INVALID [fruit.apple.texture] # you can add sub-tables smooth = true

内联表(Inline Table)

用花括号{ }提供紧凑的表写法,特别适合快速变冗长的分组嵌套数据;支持同/异行多个键值对以及末尾逗号:

name = { first = "Tom", last = "Preston-Werner" } point = {x=1, y=2} animal = { type.name = "pug" } contact = { personal = { name = "Donald Duck", email = "donald@duckburg.com", }, work = { name = "Coin cleaner", email = "donald@ScroogeCorp.com", }, }

内联表完全自包含:花括号外不能再向其中添加键或子表(type = { name = "Nail" }之后再写type.edible = false非法),反过来也不能用内联表向已定义表追加内容。

数组的表(Array of Tables)

用双括号表头[[product]]声明:首次出现定义数组及其第一个元素,之后每次出现追加一个新元素,元素按出现顺序入数组:

[[product]] name = "Hammer" sku = 738594937 [[product]] # empty table within the array [[product]] name = "Nail" sku = 284758393 color = "gray"

对应 JSON:

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

任何对"数组的表"的引用都指向最近定义的那个数组元素,因此可以在其中定义子表甚至嵌套的数组的表:

[[fruits]] name = "apple" [fruits.physical] # subtable color = "red" shape = "round" [[fruits.varieties]] # nested array of tables name = "red delicious" [[fruits.varieties]] name = "granny smith" [[fruits]] name = "banana" [[fruits.varieties]] name = "plantain"

这里有三种必须在解析期报错的非法情况,值得特别注意:

  1. 子结构先于其父数组元素定义(如先写[fruit.physical]后写[[fruit]]);
  2. 向静态定义的数组追加元素(fruits = []之后再写[[fruits]]);
  3. 同一名称在"普通表"与"数组的表"之间来回重定义([[fruits.varieties]]之后又写[fruits.varieties])。

此外,内联表可以出现在数组内部,构成"对象数组"的紧凑写法:

points = [ { x = 1, y = 2, z = 3 }, { x = 7, y = 8, z = 9 }, { x = 2, y = 4, z = 8 } ]

形式化文法:ABNF

规范文档 的最后一节明确指出:TOML 的形式化语法以独立的 ABNF 文件 呈现。该文件基于 RFC 5234 的 ABNF 格式书写,是全仓库唯一被标记为"canonical"的语法定义(见 CHANGELOG.md 中 1.0.0-rc.3 的说明),也是实现与测试器开发者的权威参考。

在 toml.abnf 中可以看到规范文本逐字对应的文法骨架:

toml = expression *( newline expression ) expression = ws [ comment ] expression =/ ws keyval ws [ comment ] expression =/ ws table ws [ comment ]

例如裸键的文法约束unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F )正是规范中"裸键只能包含A-Za-z0-9_-"的形式化表达;整数与浮点分别由dec-int / hex-int / oct-int / bin-intfloat-int-part ( exp / frac [ exp ] )定义;日期时间则完整继承了 RFC 3339 的结构:

offset-date-time = full-date time-delim full-time local-date-time = full-date time-delim partial-time local-date = full-date local-time = partial-time

ABNF 文件头部还附带了实用提示:可以通过 instaparse 之类的工具在浏览器里交互式验证文法与文档的匹配关系(将输入格式切换为 ABNF 后粘贴整个文法即可试解析)。所有合法 TOML 文档都能匹配该文法,同时规范文本补充了文法无法表达、需要语义层拒绝的规则(如重复定义键)。

版本演进与规范发布流程

本仓库同时维护了规范正文(toml.md)与 CHANGELOG.md,从中可以清晰地看到 TOML 的能力演化脉络:

  • 1.1.0(2025-12-18):内联表允许换行与末尾逗号;基础字符串新增\xHH转义;新增\e转义;日期时间与时间的秒数变为可选;
  • 1.0.0(2021-01-11):首个稳定版本,明确点号键建表语义、顶层表描述、缩进忽略等规则;
  • 0.5.0(2018-07-11):加入点号键、十六/八/二进制整数、inf/nan特殊浮点、Local Date-Time / Local Date / Local Time、ABNF 规范、.toml扩展名与application/tomlMIME 类型等;
  • 0.4.0(2015-02-12):加入内联表、数字下划线;移除正斜杠转义;
  • 0.3.0(2014-11-10):加入科学计数法、可选+前缀、RFC 3339 日期时间、多行与字面字符串;
  • 0.2.0(2013-09-24):引入"表"术语与可嵌套的表数组;
  • 0.1.0(2013-03-17):首个正式版本,版本号遵循 SemVer。

规范的发版路径被自动化在 scripts/release.py 中:它在规范仓库与 toml.io 网站仓库之间编排"更新 CHANGELOG → 打标签 → 将 toml.md 拷贝为specs/en/v{version}.md→ 更新 ABNF 链接与网站重定向 → 推送"的全流程,并在发布前校验两个仓库的 upstream 指向、分支与工作区状态(详见 docs/README.md 的 Maintainer documentation)。这也印证了仓库的自我定位:它是规范的在途(in-development)版本,已发布版本在 toml.io 网站归档。

结语:何时选择 TOML

回到 README.md 的定位:TOML 是"容易读、语义明显、无歧义映射哈希表、易于多语言解析"的最小配置格式。当你需要的是人类可维护、带注释、支持日期与嵌套结构、且有严格规范背书的配置文件时,TOML 是比 JSON(无注释)、YAML(复杂语义)和 INI(无标准、嵌套能力弱)更顺手的中间选项;而当你的需求超出"配置"范畴——例如序列化任意数据结构——则应回到通用序列化格式。本文覆盖的全部语法细节均可在本仓库的 toml.md(规范正文)与 toml.abnf(形式文法)中逐条核对,动手实现或评测解析器时请以这两份文档为准。

【免费下载链接】tomlTom's Obvious, Minimal Language项目地址: https://gitcode.com/gh_mirrors/to/toml

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询