Skip to main content
Configuration

TOML vs YAML vs JSON: config formats ranked by how much they'll annoy you

JSON is strict and comment-free, YAML is expressive and full of type-coercion footguns, TOML is calm for human-edited config. Pick by failure mode, not by how it looks.

Thien Nguyen
By Thien Nguyen
Updated July 21, 2026 · 3 min read

A team ships a config file with a list of allowed countries. It works for months. Then someone adds Norway:

countries:
  - US
  - GB
  - NO

and 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

FormatBest atThe way it'll bite you
JSONAPIs, machine-generated config, strict parsersNo comments; a single trailing comma is a hard parse error
TOMLHuman-edited app settings, flat-to-medium nestingDeeply nested structures get verbose and awkward
YAMLEcosystems 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.

References

Primary documentation and specifications checked when this article was last updated.

ConfigurationJSONDeveloper Tools

Related articles

All articles