Every project eventually needs a config file, and the choice between JSON, YAML, and TOML is rarely made deliberately. Usually you inherit whatever the framework picked. But each format makes real trade-offs, and a few of them cause bugs that are genuinely hard to debug.
This is a comparison from the perspective of someone who has to write, read, and parse these files in production — not a spec walkthrough.
The same config in all three
Start with a concrete example. Here's a small server config expressed three ways.
JSON:
json{ "name": "api-gateway", "port": 8080, "debug": false, "hosts": ["a.internal", "b.internal"], "limits": { "timeout_ms": 5000, "max_body_mb": 10 } }
YAML:
yamlname: api-gateway port: 8080 debug: false hosts: - a.internal - b.internal limits: timeout_ms: 5000 max_body_mb: 10
TOML:
tomlname = "api-gateway" port = 8080 debug = false hosts = ["a.internal", "b.internal"] [limits] timeout_ms = 5000 max_body_mb = 10
All three express the same data. The differences only matter as files grow, as humans edit them, and as machines parse untrusted input.
Readability
Readability isn't one property — it splits into writing and reviewing.
For a human typing a config by hand, YAML reads cleanest for nested data. No braces, no quotes on most keys and strings, indentation that mirrors the structure. This is why Kubernetes, GitHub Actions, and Ansible all landed on it.
For a human reviewing a diff, TOML and JSON win. YAML's reliance on indentation means a change three levels deep can look identical to a change at the top level in a diff, and a stray space changes meaning silently. TOML's flat
key = value[section]For deeply nested data, JSON and YAML scale; TOML does not. TOML was designed for flat-to-moderately-nested config, and once you're three or four levels deep its
[a.b.c][[a.b]]Comments
This is often the deciding factor and it's a short section because the answer is blunt.
- JSON has no comments. None. The spec doesn't allow them. You can fake it with a key, but that pollutes your data, and any strict parser or schema validation will choke or ignore it.text
"_comment" - YAML supports comments with .text
# - TOML supports comments with .text
#
For a file that humans maintain — explaining why a timeout is 5000ms, or flagging a value as environment-specific — the lack of comments makes raw JSON a poor hand-edited config format. This single limitation is why
.jsonIf you're working with JSON and need to reshape or validate it, a JSON formatter will at least keep the structure clean, but it won't give you comments — that's a format limitation, not a tooling gap.
Data types
What the format can represent natively matters more than people expect.
JSON has a small, well-defined type set: string, number, boolean, null, array, object. Numbers don't distinguish int from float (they're all "number"), and there's no native date type. This minimalism is a feature — there's exactly one way to read a JSON value, which is why it's the lingua franca of APIs.
YAML is a superset of JSON and adds more: it has explicit null (
null~|>&anchor*aliasTOML has the richest unambiguous type set for config: strings, integers, floats, booleans, first-class dates and times (offset datetime, local datetime, local date, local time), arrays, and tables. The date support is genuinely useful and neither JSON nor YAML matches it cleanly.
tomlreleased = 2026-06-02T09:30:00Z window = 09:00:00
If your config carries timestamps, TOML handles them with no special casing.
Failure modes
This is where the real differences show up. A format's failure modes determine how much time you lose to mysterious bugs.
YAML: significant whitespace
YAML uses indentation for structure, and it must be spaces, never tabs. Mix them and you get a parse error — if you're lucky. If you're unlucky, the indentation is valid but wrong, and your nested key silently becomes a sibling instead of a child. The file parses, the program runs, and the value just isn't where you think it is.
yamllimits: timeout_ms: 5000 max_body_mb: 10 # one space short — now a sibling of 'limits', not a child
This class of bug is hard to spot by eye and produces no error. Validating YAML structure before deploy is worth the habit; pasting it into a YAML formatter to see how the parser actually interprets the nesting will surface a misplaced key faster than rereading the raw file.
YAML: the Norway problem
The most infamous YAML gotcha. In the YAML 1.1 spec — which many widely used parsers still follow — these unquoted values are interpreted as booleans:
yamlcountries: - NO # Norway? No — this becomes the boolean false - SE - FR
NOfalseyesnoonofftruefalseynNO"NO"1.01_000version: 1.101.1YAML 1.2 narrowed boolean coercion to only
truefalseJSON: trailing commas and strictness
JSON's failure mode is the opposite — it's too strict for hand editing. A trailing comma after the last array element or object key is a syntax error. Comments are an error. A single missing quote breaks the whole parse. These are good properties for machine-to-machine data and bad ones for a file a human edits under deadline pressure. The error messages are usually precise (line and column), which softens the blow.
TOML: verbosity and nesting limits
TOML's failure modes are mild by comparison. The main one is that nested structures get verbose and the
[[array.of.tables]]Tooling and ecosystem
- JSON has universal support. Every language parses it in the standard library, every editor highlights it, and it's the native format for REST and most web tooling. Schema validation via JSON Schema is mature and widely adopted.
- YAML has strong tooling but inconsistent tooling — different parsers implement different spec versions, which is part of why the Norway problem persists. JSON Schema can validate YAML too, since YAML is a JSON superset.
- TOML has good and growing support. It's the standard for Rust () and modern Python packaging (text
Cargo.toml), and parsers exist for every major language, but it's less ubiquitous than the other two and has no equivalent to the JSON Schema ecosystem yet.textpyproject.toml
When to use each
Here's the guidance, stated plainly.
Use JSON when:
- The data is produced and consumed by machines (API payloads, build artifacts, lockfiles).
- You need a format every language reads without a dependency.
- Humans rarely hand-edit it, so the lack of comments doesn't hurt.
Use YAML when:
- Humans write and read deeply nested config by hand (CI pipelines, Kubernetes manifests, infrastructure).
- You value readability of the written form over diff clarity.
- You're prepared to quote ambiguous strings and validate indentation — and ideally enforce that in CI.
Use TOML when:
- The config is flat to moderately nested (application settings, tool configuration, package metadata).
- You want comments and unambiguous parsing with no whitespace traps.
- You have dates or want clean section grouping that reads well in diffs.
A practical rule
If a machine writes it, JSON. If a human writes a tree, YAML — with quoting discipline. If a human writes settings, TOML. Most projects end up using more than one, and that's fine: a
package.json.github/workflowspyproject.tomlThe formats aren't competing for one job. They're tuned for different ones, and matching the format to the editor — human or machine — is what keeps config maintainable.