A team ships a config file with a list of allowed countries. It works for months. Then someone adds Norway:
countries:
- US
- GB
- NOand the deploy silently drops Norway from the list. Under YAML 1.1, the bare token NO parses as the boolean false — same for yes, no, on, off, y, n. The list is now ["US", "GB", false], no parser error, no warning, just a country that quietly stops working. This is the actual difference between config formats: not how they look, but how they fail when a human types something reasonable.
The three failure modes, ranked
| Format | Best at | The way it'll bite you |
|---|---|---|
| JSON | APIs, machine-generated config, strict parsers | No comments; a single trailing comma is a hard parse error |
| TOML | Human-edited app settings, flat-to-medium nesting | Deeply nested structures get verbose and awkward |
| YAML | Ecosystems that mandate it (k8s, CI, Ansible) | Implicit typing and indentation turn typos into silent wrong values |
The honest ranking by "surprise potential": JSON annoys you loudly and immediately (it won't parse), TOML rarely annoys you at all, and YAML annoys you later, in production, which is the worst kind.
[server]
port = 8080
allowed_origins = ["https://app.example.com"]
TOML is the calm option here because everything has an explicit, obvious type and comments are allowed. port = 8080 is a number; port = "8080" is a string; there's no third thing it might secretly become.
The mistakes that actually cause outages
Trusting YAML's implicit types
The Norway problem is one instance of a whole family. version: 1.20 becomes the float 1.2 (trailing zero gone). A MAC address time: 12:00:00 can parse as a sexagesimal integer. A postal code zip: 08540 may error or drop the leading zero. Quote anything that isn't obviously and only a number, and the coercion engine leaves it alone: - "NO", version: "1.20".
Putting a trailing comma in JSON
{"a": 1, "b": 2,} is invalid JSON, full stop — no trailing commas, no comments, no unquoted keys. It's the strictest of the three and won't forgive a thing, which is exactly why it's safe for machine interchange and miserable for hand-editing.
Reaching for YAML anchors to stay DRY
&defaults / <<: *defaults merge keys feel clever until a reviewer has to trace three anchors across a 400-line file to know what a service actually gets. Anchors also aren't uniformly supported (JSON has no equivalent, some parsers reject merge keys). For config a human reads under pressure, repetition beats indirection.
Deeply nesting TOML
TOML's [table.subtable.deep] header syntax gets clumsy past two or three levels. If your config is genuinely a deep tree, that's a signal you either want YAML/JSON, or you want to flatten the structure.
Pick the format your consumers already parse, add schema or startup validation, and fail loudly on unknown keys rather than trusting a quiet default. If you're moving config between formats, a converter like json-to-yaml handles the mechanical translation — but re-check every bare no, off, and leading zero by hand, because that's precisely where an automated round-trip won't save you. The best config format is the one that makes a wrong production value hard to type by accident.
Cover photo by Pixabay on Pexels.
