Skip to main content
Configuration

YAML vs JSON vs TOML: which config format should you actually use?

JSON for machine contracts, TOML for hand-edited settings, YAML when the ecosystem forces it. The failure modes of each format matter more than which looks cleanest.

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

A team ships a country allowlist in YAML. It works in every test. Then a customer in Norway can't check out, and it takes an afternoon to find out why: the config had country: no, and the YAML parser read no as the boolean false, not the string "NO". Nobody wrote a bug — the format did. That's the "Norway problem," and it's the single best argument for treating config-format choice as an engineering decision rather than an aesthetic one.

The formats don't differ mainly in how they look. They differ in what silently goes wrong, and each has a signature failure.

The mistakes each format sets you up for

YAML coerces values you meant as strings

YAML tries to guess types, and the guesses bite. country: no and answer: yes become booleans. A version pin like version: 1.20 parses as the float 1.2 — the trailing zero vanishes. A ZIP code 07030 may lose its leading zero or, with an 8 or 9 in it, error as an invalid octal. YAML 1.2 narrowed these implicit conversions, but many popular libraries (PyYAML among them) still default to 1.1 behavior, so you can't assume you're safe. The fix is boring and reliable: quote anything that could be read as a bool, number, or date — country: "no".

YAML's whitespace is load-bearing and invisible

Indentation defines structure, so a stray tab (YAML forbids tabs for indentation and rejects the file) or a misaligned block silently reparents a key. A block scalar folded the wrong way turns a multi-line string into something you didn't write. This is why generated YAML is safer than hand-edited YAML at scale, and why every large YAML config eventually gets a schema check in CI.

JSON has no comments and no trailing commas — by design

You cannot annotate a JSON config, and the moment someone adds // TODO or leaves a trailing comma after the last array element, the parser rejects the whole file. Per RFC 8259 both are simply not part of the grammar. "JSON with comments" (JSONC, JSON5) is a different format your standard parser won't accept — don't assume JSON.parse will read your editor's tolerant version.

TOML gets ugly once nesting goes deep

TOML is genuinely pleasant for flat, hand-edited settings — a Cargo.toml, a pyproject.toml. But deeply nested structures turn into a thicket of [section.subsection.item] headers and [[arrays.of.tables]], and it stops being the readable format it was chosen for. If your config is more than two levels deep in most places, TOML is fighting you.

Choosing on failure mode, not looks

If you needPickBecause
A machine contract / API payloadJSONUnambiguous parse, universal support, strict grammar
Small hand-edited settings with commentsTOMLComments, no type-guessing surprises, flat-friendly
Kubernetes, GitHub Actions, AnsibleYAMLThe ecosystem gives you no choice
Deeply nested hand-edited dataJSON + schemaNesting stays legible; validate on load

There's a defensive rule under all of this: your format should make an invalid config fail loudly at startup or in CI, not fail quietly in production three weeks later like the Norway bug. That means a JSON Schema, a load-time validation step, and quoting ambiguous YAML values on principle. The format you pick matters far less than whether a bad value gets caught before a customer does — a converter like the YAML to JSON converter can also expose these coercions early by showing you exactly how a parser typed each value.

Cover photo by Stanislav Kondratiev on Pexels.

References

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

ConfigurationYAMLJSON

Related articles

All articles