Formatting TOML and Converting It to JSON Without the Guesswork
A practical guide to formatting TOML config, converting TOML to and from JSON, and why Cargo and pyproject picked TOML over YAML for tables and arrays.
Formatting TOML and Converting It to JSON Without the Guesswork
If you write Rust or modern Python, you already live in TOML whether you think about it or not. Cargo.toml pins every crate. pyproject.toml is now the single front door for build backends, dependencies, and tool settings that used to be scattered across setup.cfg, setup.py, and a handful of dotfiles. Hugo sites carry a config.toml, Netlify reads netlify.toml, and plenty of bots keep their wiring in a TOML file because it is the path of least resistance.
So the day-to-day questions are simple and recurring: how do I format a TOML file so a diff stays clean, and how do I move data between TOML and JSON when half my tooling speaks one and half speaks the other? This guide walks through both, with a worked example, and explains why TOML earned its place in the first place.
Why TOML Won the Config Slot
TOML's pitch is narrow and honest: it is a config format meant to be read and edited by humans, with one obvious way to write most things. The full name even spells out the goal — Tom's Obvious Minimal Language.
The reason Cargo and pyproject reached for it instead of YAML comes down to ambiguity. YAML has famous foot-guns: the Norway problem (NO parses as boolean false), significant whitespace that breaks on a stray tab, and multiple syntaxes for the same value. A config that controls your build pipeline is the last place you want a parser quietly reinterpreting version: 1.10 as the float 1.1. TOML closes those doors. Strings are quoted, types are explicit, and indentation carries no meaning — you can format it however you like and the parse result is identical.
That last point matters for the topic at hand: because whitespace is cosmetic in TOML, a formatter is free to normalize indentation and array layout without changing a single value. YAML formatters cannot make that promise.
Tables and Arrays of Tables
The piece that trips people up first is TOML's table syntax, so it is worth nailing down.
A [table] header groups the key/value pairs that follow it until the next header. So this:
[server]
host = "localhost"
port = 8080
means "inside the server table, set host and port." Dotted keys do the same nesting inline: server.host = "localhost" is identical to the block above. There is no separate flat-with-dots form once you parse it — both collapse to the same tree.
The trickier construct is the array of tables, written with double brackets [[table]]. Each [[products]] header starts a new element in the products array. This is how you express a list of structured records:
[[products]]
name = "Hammer"
sku = 738594937
[[products]]
name = "Nail"
sku = 284758393
That is an array named products with two table entries. Single brackets define one table; double brackets append to a list. Once you internalize that [[ ]] means "another item in this array," most real config files stop looking mysterious.
A Worked TOML to JSON Conversion
Here is the part that surprises people the least once they see it but the most before they do. When you convert TOML to JSON, tables become objects and arrays of tables become arrays of objects. Nothing exotic happens — the tree maps one to one.
Take this small input:
title = "Toolora"
[owner]
name = "Li Lei"
[[server]]
host = "alpha"
port = 8001
[[server]]
host = "beta"
port = 8002
Convert it and you get exactly:
{
"title": "Toolora",
"owner": {
"name": "Li Lei"
},
"server": [
{ "host": "alpha", "port": 8001 },
{ "host": "beta", "port": 8002 }
]
}
Notice the shape. The top-level keys live at the JSON root. [owner] became a nested object. [[server]] — appearing twice — became a JSON array holding two objects, in source order. The TOML Formatter + Converter does this transformation in the browser, so a Cargo.toml full of internal registry tokens never leaves your tab.
A couple of conversions are lossy by necessity, and it helps to know them up front. TOML datetimes flatten to their RFC 3339 string form because JSON has no datetime type — the spelling survives, but you parse it yourself on the way back in. And inf / nan become null, because JSON forbids both. Going the other direction, a JSON null becomes an empty string in TOML, since TOML has no null type. Those are the only real edge cases; scalars, arrays, and nested tables all round-trip cleanly.
Formatting for Clean Diffs
I reach for the formatter most often right before a commit, and the reason is mundane: I add three dependencies during a debugging session, and my Cargo.toml ends up with mixed two-space and four-space blocks plus one features array stretched to column 95. A reformat collapses all of that noise. I toggle sort-keys on so [dependencies] lands alphabetized to match the CI lint, copy it back, and the diff against main shows only the three lines I actually changed instead of forty lines of whitespace churn.
Two formatter behaviors are worth calling out. Sort-keys reorders the keys within each table — including each element of an array of tables — but it never reorders the array elements themselves. If your [[products]] list has Hammer then Nail, it stays Hammer then Nail, because TOML treats array order as data, not as cosmetic layout. The same holds for plain arrays: [3, 1, 2] stays [3, 1, 2]. The array-style toggle handles the other axis: auto inlines short arrays and expands long ones, while multi-line forces every array open so adding one dependency produces a one-line diff.
Where It Sits in the Toolchain
TOML rarely lives alone. CI configs arrive as YAML, API responses arrive as JSON, and you end up shuttling between all three. The converter handles TOML, JSON, and YAML in both directions, so a .github/workflows job matrix can become a Cargo-side config.toml with [[matrix]] entries, and a JSON bot template can become a [[commands]] array of tables that a serde derive expects.
If your day is mostly JSON, the JSON Formatter pairs naturally with this one — format and validate the JSON first, then convert it to TOML when the target runtime wants config rather than data. Same trust boundary, same in-browser guarantee: nothing you paste touches a network.
The short version: TOML is the format you keep your build settings in because it refuses to be ambiguous, [table] groups keys while [[table]] builds arrays of records, and converting to JSON simply nests those tables as objects. Once that model clicks, formatting and converting stop being a chore and become a two-second step before you push.
Made by Toolora · Updated 2026-06-13