Developer FIELD NOTES · 5 MIN READ
YAML vs TOML vs JSON: Which Config Format When
YAML, TOML and JSON compared: syntax, the Norway problem, when each format wins for config, and how to convert without losing data.
YAML, TOML, and JSON all do the same job — turn structured data into text a program can read back — but they make opposite bets about who is doing the editing. JSON optimizes for machines, YAML for humans reading deeply nested files, TOML for humans filling in flat settings. Pick the one that matches who (or what) will touch the file most.
One dataset, three dialects
Here is the same configuration in all three formats:
{
"name": "toolhub",
"retries": 3,
"tags": ["fast", "free"],
"database": { "host": "localhost", "port": 5432 }
}
name: toolhub
retries: 3
tags:
- fast
- free
database:
host: localhost
port: 5432
name = "toolhub"
retries = 3
tags = ["fast", "free"]
[database]
host = "localhost"
port = 5432
All three load to the same data structure. The differences are ergonomic: JSON quotes everything and braces everything; YAML pushes structure into indentation; TOML replaces one big tree with labeled sections. Notice the quoting rules already differ — in YAML and TOML, plain words like localhost need no quotes, and only "toolhub" needs them because a bare toolhub would be… well, fine too. Strings need quotes only when they could be mistaken for something else — which is both a feature and, as we will see, the source of YAML's most famous bugs.
YAML: indentation is the syntax
In YAML, structure comes from leading spaces. database: opens a nested block; two-space-indented keys beneath it belong to it; - marks a list item. There are no braces to misplace, which makes YAML pleasant to read — and makes a one-space typo a data corruption event, because indenting one line differently changes what it belongs to.
YAML's unquoted scalars are its sharpest edge. Values that look like words are parsed as other types in YAML 1.1 — the versions most tools still ship:
country: no # parses as boolean false — the "Norway problem"
mode: on # boolean true
version: 1.10 # number 1.1 — the trailing zero is gone
version: "1.10" # quoted: the string you meant
The Norway problem (no → false, yes → true, on/off too) and version numbers collapsing (1.10 → 1.1) are legendary in ops circles. The fix is mechanical: quote anything that is semantically a string, even if it looks like a word or a number. On the plus side, YAML has features the others lack: block scalars for multi-line strings (| keeps newlines, > folds them), anchors (&base) and aliases (*base) to reuse a block without repeating it, and multiple documents in one file separated by ---.
TOML: everything is a line
TOML is line-oriented: each line is key = value, and [section] headers define tables. Nesting is spelled out in the header rather than expressed in indentation:
[server]
host = "0.0.0.0"
port = 8080
[server.tls]
enabled = true
min_version = "1.2"
[[products]] # array of tables — each [[products]] appends one
name = "Hammer"
That explicitness kills YAML's ambiguity bugs: "1.2" is quoted, dates have native syntax (published = 1979-05-27T07:32:00Z), and there is no way for one line to accidentally belong to the wrong parent. The trade-off shows at depth — five nested levels means five header segments ([a.b.c.d.e]) — and lists of complex objects ([[products]]) read more verbosely than YAML's dash lists. That is why TOML dominates flat, setting-style config: Cargo.toml, pyproject.toml, netlify.toml.
So which one, when?
A pragmatic decision rule:
Machine-to-machine → JSON. API payloads, webhooks, log events, anything two programs exchange. Every language parses it natively, the format is strict enough that parsers agree, and strictness is a feature when neither end is human.
Humans editing deeply nested structures → YAML. Kubernetes manifests, GitHub Actions workflows, docker-compose files. Indentation beats bracket-balancing at scale, and list-of-things syntax is clean.
Humans editing flat settings → TOML. Application and package config: databases, build tools, feature flags. Every value on its own line, comments everywhere, no surprises.
And comments decide a lot: YAML and TOML allow them; JSON does not. The JSON-with-comments variants you may meet (JSONC in VS Code settings, JSON5) are convenience dialects, not interoperable standards — do not emit them at an API boundary.
Converting between them without losing data
Conversion is easy in the data, lossy around it. Three things never survive a trip through JSON, because JSON has no way to represent them: comments (gone the moment you convert YAML or TOML to JSON), YAML anchors and aliases (expanded to full copies), and formatting such as multi-line strings, which become \n escapes. Typing can shift too: a YAML version: 1.0 arrives in JSON as the number 1.0, and converting back to YAML drops the trailing zero — if 1.0 is a version string, it needed quotes in the original. Neither format carries binary data; embed small binaries as Base64 strings, which is exactly what the format in How Base64 Encoding Works is for.
The safe workflow is a loop: convert, then validate the result, then spot-check the round trip. A converter will not warn you that your comment died or your no became false — only a validation pass catches those before they reach production.
Try it yourself
Check any YAML document — including those unquoted-scalar gotchas — with the YAML Validator, which parses and reports errors with line numbers. Convert Cargo.toml-style files with the TOML to JSON Converter, and turn an API response into readable YAML with the JSON to YAML Converter — then convert it back and diff, to see exactly what a round trip preserves.