YAML, TOML, JSON5: When Each Format Wins
JSON won the data interchange war. Nothing else is close for API payloads, log lines, and machine-to-machine messages. But JSON is a bad config file format for humans, and the industry has produced three serious challengers for that role: YAML, TOML, and JSON5. They are not interchangeable, and picking the wrong one is a real source of friction.
I have shipped projects using all three, and switched between them more than once. This essay is the decision framework I now use when picking a config format for a new project.
Why JSON is a bad config format
JSON is a great data format because it is unambiguous and machine-friendly. Those same properties make it painful to hand-edit:
- No comments. You cannot explain in the file why a value is set the way it is.
- No trailing commas. Add an element to a list, you forget the comma on the previous line, you get a parse error twenty lines later.
- Quoted keys.
{"databaseHost": "localhost"}is annoying to type.{databaseHost: "localhost"}would be fine but JSON does not allow it. - No multiline strings. Pasting a regex or a SQL query into JSON requires escaping every newline.
All three contenders fix some of these. None fix all of them in the same way. The right choice depends on which fixes matter to you.
YAML: powerful, dangerous, ubiquitous
YAML is the most widely-used config format in 2026. Kubernetes uses it. Docker Compose uses it. GitHub Actions, GitLab CI, Ansible, and most cloud-native tooling default to YAML. If you are going to maintain other people's code, you will write YAML.
What YAML gets right:
- Significant indentation reduces visual noise compared to JSON's braces.
- Comments work and look natural.
- Multiline strings via
|(preserve newlines) or>(fold to spaces). - Anchors and aliases for deduplication within a file.
What YAML gets wrong (and why it has a reputation for footguns):
- The Norway problem. The country code
NOparses as the booleanfalse. Always quote string values that look like booleans. - Time zones. Bare
10:00parses as a sexagesimal number in some parsers (specifically, YAML 1.1). - Significant whitespace. Mix tabs and spaces and you will lose an afternoon.
- Two specs. YAML 1.1 and YAML 1.2 differ on type coercion. Different libraries pick different versions.
Use YAML when: you are integrating with the cloud-native ecosystem, you need multiline strings, you have deeply nested configs, or your team is already comfortable with the gotchas.
TOML: simple, predictable, less expressive
TOML was designed explicitly as a configuration format. It is what Cargo (Rust), pip (Python), and Hugo use. The selling point: there is exactly one way to write any given config, and the syntax is unambiguous on inspection.
# Cargo.toml style
[package]
name = "my-crate"
version = "0.1.0"
authors = ["Λ <[email protected]>"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = "1.34"
What TOML gets right:
- Comments work naturally.
- Trailing commas allowed.
- Native datetime types with explicit syntax.
- String types are explicit: basic, literal, multiline basic, multiline literal.
- No significant whitespace gotchas.
What TOML gets wrong:
- Deeply nested structures get awkward. Three or four levels of nesting is the practical limit before TOML's syntax becomes worse than JSON.
- Less library support outside the Rust and Python ecosystems.
- Arrays of tables (
[[entries]]) syntax takes getting used to.
Use TOML when: your config is mostly flat with a few nested sections, you are in the Rust or Python ecosystem, or you value predictability over expressiveness.
JSON5: JSON with the rough edges sanded off
JSON5 is JSON plus a small set of permissive extensions: comments, trailing commas, unquoted keys, single-quoted strings, and a few number-syntax additions. It compiles to JSON; any JSON5 file can be converted to JSON losslessly minus the comments.
{
// Comments work
databaseHost: 'localhost', // unquoted keys, single quotes
port: 5432,
maxConnections: 100, // trailing comma OK
timeout: +Infinity, // number syntax extensions
}
What JSON5 gets right:
- It is a strict superset of JSON. Existing JSON files work as-is.
- Migration cost is zero: rename
.jsonto.json5and start adding comments. - Trivial to remember if you already know JSON.
What JSON5 gets wrong:
- Library support is sparse compared to YAML and TOML.
- Less ecosystem momentum. Many tools accept JSON or YAML but not JSON5.
- No multiline strings (the JSON5 spec does not add them).
Use JSON5 when: your config is structurally JSON-shaped but you want comments, you are in an ecosystem where YAML or TOML support is patchy, or you are migrating an existing JSON config.
The decision matrix
- Cloud-native tool config (Kubernetes, CI/CD, infra-as-code)? YAML. Fighting the ecosystem is not worth it.
- Rust or Python package config, or anything resembling a manifest? TOML. The ecosystem expects it.
- Existing JSON config you want to annotate? JSON5. Zero migration cost.
- Deeply nested data with many references? YAML, using anchors and aliases.
- Brand new project, flat config, want predictable parsing? TOML.
- You need to ship the config to a service that only accepts JSON? Plain JSON and accept the pain, or use JSON5 with a conversion step in your build.
The conversion problem
The reality is that real projects often need to convert between these. CI pipelines read YAML and emit JSON for downstream tools. A team writes config in TOML and ships JSON to a frontend. The JSON/YAML converter and TOML converter on BoltQuickTools handle these conversions in the browser without uploading anything.
Common conversion gotchas:
- YAML to JSON: YAML supports keys that are not strings (booleans, numbers). JSON does not. Conversion may need to coerce.
- JSON to YAML: large multiline strings should ideally use YAML's
|block style for readability, not single-line escaped strings. - TOML to JSON: TOML's datetime types become strings; you lose type information unless your JSON consumer uses a schema.
- JSON5 to JSON: comments are stripped. Make sure they are not load-bearing for code review.
What I use today
For BoltQuickTools the tool manifest is in YAML because I started in a Python ecosystem and PyYAML was already a dependency. For a Rust side project I would reach for TOML. For an existing JSON config I needed to annotate, I would convert to JSON5 first and write the comments. There is no globally correct answer; the right format is the one that matches your ecosystem and your config's structure.