Skip to content

TOML

Model mapping

Tables and array-of-tables map directly onto the Document model. A table becomes a list of edges. [[x]] written twice becomes the label x twice.

TOML Document
a = 1 / b = 2 [(a,1),(b,2)]
[[x]] twice, each with name [(x,[(name,"a")]), (x,[(name,"b")])]

[[x]] is TOML's idiomatic repeated record, and it lands on the repeated-label shape with no adjustment at all. Of the four non-native formats, TOML is the one whose own idiom already matches the model.

TOML has native date, time, and datetime literals. All three parse to the matching types with no schema needed, and write back the same way, with one exception: TOML has no time of day with an offset, so a writer writes that as a quoted ISO-8601 string and reports format.temporal-stringified (E-37). Every other temporal is written as its native literal, seconds included, unaltered.

TOML has no null. A null-valued leaf cannot be written, and there is no substitute value to fall back to — dropping the edge silently would erase its existence, not just its value. Writing one MUST fail (write.unsupported-value, C-10), not silently omit it.

Top level must be a table. A bare scalar Document cannot be written as TOML.

Interleaving is lost on write, as in the rest of the JSON family.

Duplicate keys are rejected (C-12). A table that defines a key twice (a = 1 then a = 2, a and "a" included) fails the read with parse.codec-syntax; TOML itself calls it invalid, and Omnist does not turn it into JSON's last-value-wins (§ JSON, "Duplicate keys"). The same key in two different tables is not a repeat, and [[x]] written twice is the repeated-label shape, not a duplicate.

Integers and floats. TOML hexadecimal (0x), octal (0o) and binary (0b) integers and _-separated digits are integers like any other, and D-9's digit limit counts the digits of their value (D-28): 0b followed by 4 301 ones is a value of 1 295 decimal digits and is within the default limit. An integer is arbitrary precision, so one beyond 64 bits is read, not refused. A float literal beyond binary64's range, 1e999, reads as +Infinity with no diagnostic (D-29).

Worked example

The schema:

record Address  { "street": string, "city": string }
record LineItem { "sku": string, "qty": integer, "price": number }

record Order {
    "id":           string,
    "status":       string,
    "total":        number,
    "address":      Address,
    "items" [1,]:   LineItem,
    "coupon" [0,1]: string,
}

record Root { "order": Order }
root Root

The same order in TOML:

[order]
id = "A1"
status = "shipped"
total = 29.97

[order.address]
street = "1 Main"
city = "London"

[[order.items]]
sku = "W"
qty = 3
price = 9.99

[[order.items]]
sku = "G"
qty = 1
price = 9.99

reads to:

[ (order, [ (id,      "A1"),
            (status,  "shipped"),
            (total,   29.97),
            (address, [ (street, "1 Main"), (city, "London") ]),
            (items,   [ (sku, "W"), (qty, 3), (price, 9.99) ]),
            (items,   [ (sku, "G"), (qty, 1), (price, 9.99) ]) ]) ]

The [[order.items]] blocks are the clearest statement in any format of what "items" [1,]: LineItem means: the label repeats, and there is no list. [order.address] — a single table, not an array-of-tables — is the [1,1] case, one edge.

A coupon field would be a plain key inside [order]. Because TOML has no null, an absent coupon is written by omitting the key, which is what [0,1] cardinality already means. A null coupon has no TOML spelling at all — writing one fails, it is not written as if it had been omitted.

A real-world example. pyproject.toml is the canonical case of a spec'd-open format: its core tables are specified, but [tool.*] is defined to be open — third-party tools own those subtrees, and a schema author doesn't get to close them. See pyproject.osd and its three fixtures in ../examples/, framed explicitly around where OSD's algebra can't fully capture the format's own spec.

Parity gaps

Chapter 9's status table (§9.3) is the authority on which implementations ship a TOML codec. This page states no per-port status of its own: a copy here can only go stale relative to the ledger.

There is no TOML-specific entry in §9.4. The null gap and the table-at-top-level rule are properties of TOML, specified above, not open questions between implementations.