v1.0.0
发布于 2020年4月3日 – 文本版本

TOML v1.0.0-rc.1

Tom 的显而易见、最简语言。

由 Tom Preston-Werner、Pradyun Gedam 等人编写。

目标

TOML 旨在成为一种最简的配置文件格式,由于其明显的语义而易于阅读。TOML 旨在无歧义地映射到哈希表。TOML 应该易于解析为各种语言中的数据结构。

示例

# 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 = [ 8001, 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"
]

规范

  • TOML 区分大小写。
  • TOML 文件必须是有效的 UTF-8 编码的 Unicode 文档。
  • 空格表示制表符 (0x09) 或空格 (0x20)。
  • 换行符表示 LF (0x0A) 或 CRLF (0x0D 0x0A)。

注释

井号标记该行其余部分为注释,除非在字符串内部。

# This is a full-line comment
key = "value"  # This is a comment at the end of a line
another = "# This is not a comment"

注释中不允许使用除制表符以外的控制字符 (U+0000 至 U+0008、U+000A 至 U+001F、U+007F)。

键值对

TOML 文档的主要构建块是键值对。

键位于等号左侧,值位于右侧。键名和值周围的空格将被忽略。键、等号和值必须位于同一行(尽管某些值可以跨多行)。

key = "value"

值必须具有以下类型之一。

未指定的value是无效的。

key = # INVALID

键值对后面必须有一个换行符。(有关例外情况,请参阅内联表。)

first = "Tom" last = "Preston-Werner" # INVALID

键可以是裸键、带引号的键或点分键。

**裸键**只能包含 ASCII 字母、ASCII 数字、下划线和连字符 (A-Za-z0-9_-)。请注意,裸键允许仅由 ASCII 数字组成,例如 1234,但始终被解释为字符串。

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

**带引号的键**遵循与基本字符串或字面字符串完全相同的规则,并允许您使用更广泛的键名集。最佳实践是使用裸键,除非绝对必要。

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

裸键必须是非空的,但允许使用空带引号的键(但不鼓励)。

= "no key name"  # INVALID
"" = "blank"     # VALID but discouraged
'' = 'blank'     # VALID but discouraged

**点分键**是由用点连接的一系列裸键或带引号的键组成。这允许将类似的属性组合在一起

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

在 JSON 中,这将为您提供以下结构

{
  "name": "Orange",
  "physical": {
    "color": "orange",
    "shape": "round"
  },
  "site": {
    "google.com": true
  }
}

点分部分周围的空格将被忽略,但是,最佳实践是不使用任何多余的空格。

多次定义键是无效的。

# DO NOT DO THIS
name = "Tom"
name = "Pradyun"

由于裸键允许仅由 ASCII 整数组成,因此可以编写看起来像浮点数但实际上是两部分点分键的点分键。除非您有充分的理由(您可能没有),否则不要这样做。

3.14159 = "pi"

上面的 TOML 映射到以下 JSON。

{ "3": { "14159": "pi" } }

只要键尚未直接定义,您仍然可以写入它以及其中的名称。

# 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
# THE FOLLOWING IS INVALID

# This defines the value of fruit.apple to be an integer.
fruit.apple = 1

# But then this treats fruit.apple like it's a table.
# You can't turn an integer into a table.
fruit.apple.smooth = true

不建议以乱序定义点分键。

# VALID BUT DISCOURAGED

apple.type = "fruit"
orange.type = "fruit"

apple.skin = "thin"
orange.skin = "thick"

apple.color = "red"
orange.color = "orange"
# RECOMMENDED

apple.type = "fruit"
apple.skin = "thin"
apple.color = "red"

orange.type = "fruit"
orange.skin = "thick"
orange.color = "orange"

字符串

有四种方法可以表达字符串:基本字符串、多行基本字符串、字面字符串和多行字面字符串。所有字符串都必须仅包含有效的 UTF-8 字符。

**基本字符串**用引号括起来。可以使用任何 Unicode 字符,但必须转义的字符除外:引号、反斜杠和除制表符以外的控制字符 (U+0000 至 U+0008、U+000A 至 U+001F、U+007F)。

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

为了方便起见,一些常用的字符具有紧凑的转义序列。

\b         - backspace       (U+0008)
\t         - tab             (U+0009)
\n         - linefeed        (U+000A)
\f         - form feed       (U+000C)
\r         - carriage return (U+000D)
\"         - quote           (U+0022)
\\         - backslash       (U+005C)
\uXXXX     - unicode         (U+XXXX)
\UXXXXXXXX - unicode         (U+XXXXXXXX)

可以使用 \uXXXX\UXXXXXXXX 形式转义任何 Unicode 字符。转义代码必须是有效的 Unicode 标量值

上面未列出的所有其他转义序列都已保留,如果使用,TOML 应该产生错误。

有时您需要表达文本段落(例如翻译文件)或希望将很长的字符串分解成多行。TOML 使这变得很容易。

**多行基本字符串**两侧用三个引号括起来,并允许换行符。紧跟在起始分隔符后面的换行符将被修剪。所有其他空格和换行符字符将保持不变。

str1 = """
Roses are red
Violets are blue"""

TOML 解析器可以自由地将换行符规范化为其平台上的任何有意义的换行符。

# On a Unix system, the above multi-line string will most likely be the same as:
str2 = "Roses are red\nViolets are blue"

# On a Windows system, it will most likely be equivalent to:
str3 = "Roses are red\r\nViolets are blue"

要编写不引入多余空格的长字符串,请使用“行尾反斜杠”。当一行上的最后一个非空格字符是 \ 时,它将与所有空格(包括换行符)一起被修剪,直到下一个非空格字符或结束分隔符。所有对基本字符串有效的转义序列也对多行基本字符串有效。

# The following strings are byte-for-byte equivalent:
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.\
       """

可以使用任何 Unicode 字符,但必须转义的字符除外:反斜杠和除制表符、换行符和回车符以外的控制字符 (U+0000 至 U+0008、U+000B、U+000C、U+000E 至 U+001F、U+007F)。

您可以在多行基本字符串中的任何位置编写一个引号或两个相邻的引号。它们也可以正好写在分隔符内部。

str4 = """Here are two quotation marks: "". Simple enough."""
# str5 = """Here are three quotation marks: """."""  # INVALID
str5 = """Here are three quotation marks: ""\"."""
str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\"."""

# "This," she said, "is just a pointless statement."
str7 = """"This," she said, "is just a pointless statement.""""

如果您经常指定 Windows 路径或正则表达式,那么必须转义反斜杠很快就会变得乏味且容易出错。为了提供帮助,TOML 支持根本不允许转义的字面字符串。

**字面字符串**用单引号括起来。与基本字符串一样,它们必须出现在一行上

# What you see is what you get.
winpath  = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted   = 'Tom "Dubs" Preston-Werner'
regex    = '<\i\c*\s*>'

由于没有转义,因此无法在用单引号括起来的字面字符串内编写单引号。幸运的是,TOML 支持字面字符串的多行版本,该版本解决了此问题。

**多行字面字符串**两侧用三个单引号括起来,并允许换行符。与字面字符串一样,根本没有转义。紧跟在起始分隔符后面的换行符将被修剪。分隔符之间的所有其他内容都按原样解释,无需修改。

regex2 = '''I [dw]on't need \d{2} apples'''
lines  = '''
The first newline is
trimmed in raw strings.
   All other whitespace
   is preserved.
'''

您可以在多行字面字符串中的任何位置编写 1 个或 2 个单引号,但不允许使用三个或更多个单引号的序列。

quot15 = '''Here are fifteen quotation marks: """""""""""""""'''

# apos15 = '''Here are fifteen apostrophes: ''''''''''''''''''  # INVALID
apos15 = "Here are fifteen apostrophes: '''''''''''''''"

# 'That's still pointless', she said.
str = ''''That's still pointless', she said.'''

字面字符串中不允许使用除制表符以外的控制字符。因此,对于二进制数据,建议您使用 Base64 或其他合适的 ASCII 或 UTF-8 编码。该编码的处理方式将是特定于应用程序的。

整数

整数是整数。正数可以以加号为前缀。负数以减号为前缀。

int1 = +99
int2 = 42
int3 = 0
int4 = -17

对于大数,您可以在数字之间使用下划线来提高可读性。每个下划线两侧都必须至少有一个数字。

int5 = 1_000
int6 = 5_349_221
int7 = 1_2_3_4_5     # VALID but discouraged

不允许使用前导零。整数value -0+0 是有效的,并且与没有前缀的零相同。

非负整数value也可以用十六进制、八进制或二进制表示。在这些格式中,不允许使用前导 +,并且允许使用前导零(在前缀之后)。十六进制值不区分大小写。下划线允许在数字之间(但在前缀和值之间不允许)。

# hexadecimal with prefix `0x`
hex1 = 0xDEADBEEF
hex2 = 0xdeadbeef
hex3 = 0xdead_beef

# octal with prefix `0o`
oct1 = 0o01234567
oct2 = 0o755 # useful for Unix file permissions

# binary with prefix `0b`
bin1 = 0b11010110

预期为 64 位(带符号长)范围(−9,223,372,036,854,775,808 至 9,223,372,036,854,775,807)。

浮点数

浮点数应实现为 IEEE 754 binary64 值。

浮点数由整数部分(遵循与十进制整数value相同的规则)后跟小数部分和/或指数部分组成。如果同时存在小数部分和指数部分,则小数部分必须位于指数部分之前。

# fractional
flt1 = +1.0
flt2 = 3.1415
flt3 = -0.01

# exponent
flt4 = 5e+22
flt5 = 1e06
flt6 = -2E-2

# both
flt7 = 6.626e-34

小数部分是小数点后跟一个或多个数字。

指数部分是 E(大写或小写)后跟整数部分(遵循与十进制整数value相同的规则,但可以包含前导零)。

与整数类似,您可以使用下划线来提高可读性。每个下划线两侧都必须至少有一个数字。

flt8 = 224_617.445_991_228

浮点数value -0.0+0.0 是有效的,应根据 IEEE 754 映射。

也可以表达特殊的浮点数value。它们始终为小写。

# infinity
sf1 = inf  # positive infinity
sf2 = +inf # positive infinity
sf3 = -inf # negative infinity

# not a number
sf4 = nan  # actual sNaN/qNaN encoding is implementation specific
sf5 = +nan # same as `nan`
sf6 = -nan # valid, actual encoding is implementation specific

布尔值

布尔值只是您习惯使用的标记。始终为小写。

bool1 = true
bool2 = false

带偏移量的日期时间

为了明确表示特定的时间点,您可以使用 RFC 3339 格式的带偏移量的日期时间。

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

为了提高可读性,您可以用空格替换日期和时间之间的 T 分隔符(如 RFC 3339 第 5.6 节所允许)。

odt4 = 1979-05-27 07:32:00Z

分数秒的精度是特定于实现的,但至少需要毫秒精度。如果value包含超出实现支持的精度,则必须截断多余的精度,而不是四舍五入。

本地日期时间

如果您从 RFC 3339 格式的日期时间中省略偏移量,它将表示给定的日期时间,而与偏移量或时区无关。它无法在没有其他信息的情况下转换为时间点。如果需要,转换为时间点是特定于实现的。

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

分数秒的精度是特定于实现的,但至少需要毫秒精度。如果value包含超出实现支持的精度,则必须截断多余的精度,而不是四舍五入。

本地日期

如果您只包含 RFC 3339 格式的日期时间中的日期部分,它将表示整个日期,而与偏移量或时区无关。

ld1 = 1979-05-27

本地时间

如果您只包含 RFC 3339 格式的日期时间中的时间部分,它将表示一天中的那个时间,而与特定日期或任何偏移量或时区无关。

lt1 = 07:32:00
lt2 = 00:32:00.999999

分数秒的精度是特定于实现的,但至少需要毫秒精度。如果value包含超出实现支持的精度,则必须截断多余的精度,而不是四舍五入。

数组

数组是用方括号括起来的内部包含值的结构。空格将被忽略。元素用逗号分隔。数组可以包含与键值对中允许的相同数据类型的value。可以混合不同类型的value。

integers = [ 1, 2, 3 ]
colors = [ "red", "yellow", "green" ]
nested_array_of_int = [ [ 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 <[email protected]>",
  { name = "Baz Qux", email = "[email protected]", url = "https://example.com/bazqux" }
]

数组可以跨越多行。在数组的最后一个value之后允许使用终止逗号(也称为尾随逗号)。在value之前和结束括号之前可以有任意数量的换行符和注释。

integers2 = [
  1, 2, 3
]

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

表(也称为哈希表或字典)是键值对的集合。它们单独出现在一行上的方括号中。您可以将它们与数组区分开来,因为数组永远只是value。

[table]

在它下面,以及直到下一个表或文件结尾,都是该表的键值。表中的键值对不保证以任何特定的顺序排列。

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

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

表的命名规则与键相同(请参阅上面键的定义)。

[dog."tater.man"]
type.name = "pug"

在 JSON 中,这将为您提供以下结构

{ "dog": { "tater.man": { "type": { "name": "pug" } } } }

键周围的空格将被忽略,但是,最佳实践是不使用任何多余的空格。

[a.b.c]            # this is best practice
[ d.e.f ]          # same as [d.e.f]
[ g .  h  . i ]    # same as [g.h.i]
[ j . "ʞ" . 'l' ]  # same as [j."ʞ".'l']

如果您不想指定所有上级表,则无需指定。TOML 知道如何为您完成。

# [x] you
# [x.y] don't
# [x.y.z] need these
[x.y.z.w] # for this to work

[x] # defining a super-table afterwards is ok

允许使用空表,并且它们只是在其中没有任何键值对。

与键一样,您不能多次定义任何表。这样做是无效的。

# DO NOT DO THIS

[fruit]
apple = "red"

[fruit]
orange = "orange"
# DO NOT DO THIS EITHER

[fruit]
apple = "red"

[fruit.apple]
texture = "smooth"

不建议以乱序定义表。

# VALID BUT DISCOURAGED
[fruit.apple]
[animal]
[fruit.orange]
# RECOMMENDED
[fruit.apple]
[fruit.orange]
[animal]

点分键将每个点左侧的所有内容定义为表。由于表不能多次定义,因此不允许使用 [table] 标题重新定义此类表。同样,不允许使用点分键重新定义已在 [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

内联表

内联表提供了一种更紧凑的语法来表达表格。它们尤其适用于分组数据,否则这些数据可能会很快变得冗长。内联表用花括号{}括起来。在花括号内,可以出现零个或多个用逗号分隔的键/值对。键/值对采用与标准表中键/值对相同的形式。所有值类型都允许,包括内联表。

内联表旨在出现在一行上。在内联表的最后一个键/值对之后不允许使用终止逗号(也称为尾随逗号)。在花括号之间不允许换行,除非它们在值内有效。即使如此,也强烈建议不要将内联表拆分成多行。如果您发现自己渴望这样做,则意味着您应该使用标准表。

name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
animal = { type.name = "pug" }

上面的内联表与以下标准表定义相同

[name]
first = "Tom"
last = "Preston-Werner"

[point]
x = 1
y = 2

[animal]
type.name = "pug"

内联表完全定义了它们内部的键和子表。不能向其中添加新的键和子表。

[product]
type = { name = "Nail" }
# type.edible = false  # INVALID

类似地,内联表不能用于向已定义的表添加键或子表。

[product]
type.name = "Nail"
# type = { edible = false }  # INVALID

表数组

尚未表达的最后一种类型是表数组。这些可以通过在双括号中使用表名来表达。在其下方,以及直到下一个表或文件结尾,都是该表的键/值。每个具有相同双括号名称的表都将是表数组中的一个元素。表按遇到的顺序插入。没有键/值对的双括号表将被视为空表。

[[products]]
name = "Hammer"
sku = 738594937

[[products]]

[[products]]
name = "Nail"
sku = 284758393

color = "gray"

在 JSON 世界中,这将为您提供以下结构。

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

您也可以创建嵌套的表数组。只需在子表上使用相同的双括号语法即可。每个双括号子表都属于最近定义的表元素。普通子表(不是数组)同样属于最近定义的表元素。

[[fruit]]
  name = "apple"

  [fruit.physical]  # subtable
    color = "red"
    shape = "round"

  [[fruit.variety]]  # nested array of tables
    name = "red delicious"

  [[fruit.variety]]
    name = "granny smith"

[[fruit]]
  name = "banana"

  [[fruit.variety]]
    name = "plantain"

上面的 TOML 映射到以下 JSON。

{
  "fruit": [
    {
      "name": "apple",
      "physical": {
        "color": "red",
        "shape": "round"
      },
      "variety": [
        { "name": "red delicious" },
        { "name": "granny smith" }
      ]
    },
    {
      "name": "banana",
      "variety": [
        { "name": "plantain" }
      ]
    }
  ]
}

如果表或表数组的父级是数组元素,则必须在定义子级之前已定义该元素。尝试反转该顺序必须在解析时产生错误。

# INVALID TOML DOC
[fruit.physical]  # subtable, but to which parent element should it belong?
  color = "red"
  shape = "round"

[[fruit]]  # parser must throw an error upon discovering that "fruit" is
           # an array rather than a table
  name = "apple"

尝试追加到静态定义的数组,即使该数组为空或类型兼容,也必须在解析时产生错误。

# INVALID TOML DOC
fruit = []

[[fruit]] # Not allowed

尝试使用与已建立数组相同的名称定义普通表必须在解析时产生错误。尝试将普通表重新定义为数组也必须产生解析时错误。

# INVALID TOML DOC
[[fruit]]
  name = "apple"

  [[fruit.variety]]
    name = "red delicious"

  # INVALID: This table conflicts with the previous array of tables
  [fruit.variety]
    name = "granny smith"

  [fruit.physical]
    color = "red"
    shape = "round"

  # INVALID: This array of tables conflicts with the previous table
  [[fruit.physical]]
    color = "green"

您也可以在适当的地方使用内联表

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

文件名扩展名

TOML 文件应使用扩展名.toml

MIME 类型

通过互联网传输 TOML 文件时,合适的 MIME 类型是application/toml

与其他格式的比较

TOML 与其他用于应用程序配置和数据序列化的文件格式(例如 YAML 和 JSON)共享特性。TOML 和 JSON 都是简单的,并且使用普遍存在的数据类型,使它们易于机器编码或解析。TOML 和 YAML 都强调人类可读性功能,例如注释,这使得更容易理解给定行的目的。TOML 的区别在于它将这些功能结合起来,允许注释(不像 JSON),但保留了简单性(不像 YAML)。

由于 TOML 明确旨在用作配置文件格式,因此解析它很容易,但它并非旨在序列化任意数据结构。TOML 始终在文件的顶层有一个哈希表,该表可以轻松地在它的键内嵌套数据,但它不允许顶层数组或浮点数,因此它不能直接序列化某些数据。也没有标准来识别 TOML 文件的开始或结束,这可能会使它通过流发送变得复杂。这些细节必须在应用程序层协商。

INI 文件通常因其在语法和用作配置文件方面的相似性而与 TOML 进行比较。但是,INI 没有标准化的格式,并且它们不能优雅地处理超过一两级的嵌套。

进一步阅读

参与进来

欢迎提供文档、错误报告、拉取请求以及所有其他贡献!

维基

我们有一个官方 TOML Wiki,其中编目了以下内容

  • 使用 TOML 的项目
  • 实现
  • 验证器
  • TOML 解码器和编码器的与语言无关的测试套件
  • 编辑器支持
  • 编码器
  • 转换器

如果您想查看或添加到该列表,请查看。感谢您成为 TOML 社区的一员!