YAML vs JSON vs TOML — Which Config Format Should You Actually Use?
YAML, JSON, and TOML all store config. They are not interchangeable. YAML will silently turn your country code into a boolean. JSON won't let you leave a comment. TOML is the one nobody on your team has used before. Here's how to pick the right one.
In 2015, a Ansible playbook broke because of this line:
country: NO
The config loaded without errors. No parser complaints. But country wasn’t set to the string "NO". It was set to false. Because in YAML 1.1, NO is a boolean. So is yes, on, off, ye, e n. This is the Norway Problem, and it has been silently corrupting configs for years.
This is not a YAML bug report. It’s a framing for the decision you’re about to make: YAML, JSON, or TOML for your next config file? Each has real trade-offs, and the “just use what the ecosystem uses” answer doesn’t always apply.
The Same Config, Three Ways
Before the breakdown, here’s the same app configuration written in all three formats:
YAML
# App configuration
app:
name: my-api
port: 8080
debug: false
database:
host: localhost
port: 5432
name: mydb
pool_size: 10
logging:
level: info
format: json
outputs:
- stdout
- /var/log/app.log
JSON
{
"app": {
"name": "my-api",
"port": 8080,
"debug": false
},
"database": {
"host": "localhost",
"port": 5432,
"name": "mydb",
"pool_size": 10
},
"logging": {
"level": "info",
"format": "json",
"outputs": [
"stdout",
"/var/log/app.log"
]
}
}
TOML
# App configuration
[app]
name = "my-api"
port = 8080
debug = false
[database]
host = "localhost"
port = 5432
name = "mydb"
pool_size = 10
[logging]
level = "info"
format = "json"
outputs = ["stdout", "/var/log/app.log"]
YAML is the most compact but the most syntactically loaded. JSON is the most verbose but the most explicit. TOML sits in the middle: readable without YAML’s implicit type conversions.
YAML: Powerful, Permissive, and Full of Traps
YAML is the default choice for CI/CD pipelines (GitHub Actions, GitLab CI, CircleCI), Kubernetes manifests, Ansible playbooks, and most developer tools. You don’t really choose YAML — it chooses you.
The footguns, documented:
1. The Boolean Problem (The Norway Problem)
YAML 1.1 — the spec that most parsers actually implement — treats an alarming number of strings as booleans:
# YAML 1.1 boolean values (all parsed as true or false)
enabled: yes # true
disabled: no # false
active: on # true
paused: off # false
valid: true # true
invalid: false # false
# The Norway Problem in practice:
country_codes:
NO: Norway # Key "NO" is fine, but value "NO" becomes false
SE: Sweden
YES: Yemen # "YES" also becomes true
# The fix: quote your strings
country_codes:
NO: "Norway"
SE: "Sweden"
YAML 1.2 (released 2009) fixed this — only true e false are booleans. The problem is that PyYAML didn’t fully adopt 1.2 behavior until version 6.0 in 2021, and Go’s popular gopkg.in/yaml.v2 still uses 1.1 semantics as of 2024. If you’re using Ruby’s Psych < 4.0 or any pre-6.0 PyYAML, you’re on 1.1.
2. Tabs Will Kill Your Config
YAML disallows tab characters for indentation. Only spaces are valid. Your editor might display tabs and spaces identically, the file might look correct, and YAML will still throw:
yaml.scanner.ScannerError: while scanning a block mapping
found character '\t' that cannot start any token
This is the error that makes junior developers question their career choices. Configure your editor to expand tabs to spaces in YAML files. Every editor supports this; not every editor has it on by default.
3. Multiline Strings Are Not Obvious
# | (literal block): preserves newlines exactly
description: |
Line one.
Line two.
Line three.
# Result: "Line one.\nLine two.\nLine three.\n"
# > (folded block): folds newlines into spaces
short_desc: >
This will all become
one long line.
# Result: "This will all become one long line.\n"
# Trailing newlines: | adds one, |+ adds all, |- strips them all
desc_stripped: |-
No trailing newline.
Nobody memorizes this without looking it up. The mnemonic I use: | looks like a line break, > looks like something being squished together. It’s still confusing three years in.
When YAML Wins
- You’re writing Kubernetes manifests, GitHub Actions workflows, or Ansible playbooks — you have no choice.
- Your config has lots of comments explaining non-obvious values. YAML supports inline comments; JSON and TOML do too, but YAML feels most natural for annotation-heavy configs.
- Your data has deeply nested structures that would look awful with TOML’s flat tables.
- The team is already fluent and has a linter (yamllint) in the pipeline.
JSON: The Boring, Reliable Workhorse
JSON was designed as a data interchange format, not a config format. Douglas Crockford deliberately left out comments — his argument was that comments would be used for directives that parsers would differ on. This is why package.json has no comments and tsconfig.json is technically JSON with Comments (JSONC), which is a separate thing most JSON parsers don’t support.
JSON’s real problems for config files:
- No comments. You cannot explain why
"maxRetries": 3and not 5. You cannot leave a TODO. You cannot mark a field as deprecated. This is genuinely painful for config files that outlive their authors. - No trailing commas. Adding an item to an array means modifying the previous line to add a comma. Every JSON diff becomes a 2-line change. Every merge conflict is slightly worse than it needs to be.
- Verbose for nested data. Six lines of braces and brackets for what YAML does in three lines of indentation.
- All numbers are the same type. JSON doesn’t distinguish integers from floats.
1e1.0are both just numbers, and what your language deserializes them as depends on the parser.
But JSON’s predictability is also its main feature. Every language has a JSON parser. The spec is unambiguous. There are no implicit type conversions. A string is always a string — "yes" will never silently become true. If you need to validate a JSON config programmatically, IO Tools’ JSON Formatter can catch syntax errors before they hit production — useful when someone edits a config by hand and forgets a trailing comma.
When JSON Wins
- API responses or configs that are consumed by multiple languages/services. JSON is universal; TOML support is patchy in some ecosystems.
- You need strict type guarantees. JSON Schema validation is mature, well-supported, and widely used (VS Code uses it for settings autocomplete).
- The config is machine-generated. Nobody writes JSON by hand if they can avoid it — but machines generate it just fine.
- You’re working in Node.js or front-end JavaScript, where JSON is a native citizen.
TOML: Opinionated Config, Done Right
TOML (Tom’s Obvious, Minimal Language) was created by Tom Preston-Werner, GitHub’s co-founder, specifically for config files. It reached v1.0 in January 2021. It’s the default format for Rust’s Cargo.toml, Python’s pyproject.toml, and Hugo static sites.
TOML’s design philosophy: types should be explicit, structure should be flat where possible, and there should be exactly one obvious way to write any config value.
# Types are unambiguous in TOML
name = "my-app" # string: always quoted
port = 8080 # integer
threshold = 3.14 # float
enabled = true # boolean: only true/false, no yes/no
created = 2024-01-15 # date: native type
tags = ["api", "prod"] # array
# "yes" is just a string. Always.
country = "NO" # string "NO", no boolean nonsense
The rough edges:
- Arrays of tables syntax is genuinely awkward.
[[products]]e[products.details]look similar but behave completely differently. The spec makes sense; the visual distinction doesn’t. - Deep nesting gets verbose. What YAML does in 5 indented lines, TOML does in 3 separate section headers. For configs more than 3 levels deep, TOML starts to feel like the wrong tool.
- Parser availability. TOML parsers exist for every major language, but they vary in spec compliance. The TOML compliance test suite regularly reveals edge cases. JSON parsers are battle-tested by orders of magnitude more usage.
- Team familiarity. If you use TOML outside the Rust or Python ecosystem, expect at least one teammate to open a PR with “what is this format?”
When TOML Wins
- Rust projects —
Cargo.tomlis the standard and the tooling is excellent. - Python projects using
pyproject.toml(PEP 518) — this is now the recommended home for tool config like Black, Ruff, mypy, and pytest. - Simple, flat configs where YAML’s indentation-sensitivity would be a liability.
- You want native date/time support without serializing to strings.
The Quick Decision Guide
- Kubernetes / CI/CD pipeline / Ansible? YAML. No choice.
- API config consumed by multiple services in different languages? JSON.
- Rust project? TOML (Cargo.toml convention).
- Python project config (linters, formatters, build tools)? TOML (pyproject.toml is the standard now).
- Static site config (Hugo, Zola)? TOML, though these usually support all three.
- Node.js project config? JSON (package.json ecosystem), or YAML if you need comments.
- Humans will edit this frequently and need to leave notes? YAML or TOML (both support comments). Not JSON.
- You want strict type safety and schema validation? JSON + JSON Schema.
The honest answer for most new projects: use whatever the primary language’s ecosystem expects. Rust expects TOML. Python tools expect TOML or YAML. Node.js expects JSON. If you’re writing something language-agnostic, TOML for human-edited config and JSON for machine-generated/consumed config is a reasonable default split.
Instale nossas extensões
Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida
恵 O placar chegou!
Placar é uma forma divertida de acompanhar seus jogos, todos os dados são armazenados em seu navegador. Mais recursos serão lançados em breve!
Ferramentas essenciais
Ver tudo Novas chegadas
Ver tudoAtualizar: Nosso ferramenta mais recente was added on Mai 31, 2026
