Don't like ads? Go Ad-Free Today

TOML vs YAML vs JSON — Config Formats Ranked by How Much They’ll Annoy You

Updated on

Every config format will eventually betray you. YAML with indentation hell and silent boolean coercions, JSON with its no-comments policy, TOML with its 'wait, what's this syntax?' moment. Here's the realistic breakdown of what each one costs you — and when to pick which.

TOML vs YAML vs JSON — Config Formats Ranked by How Much They'll Annoy You 1
ADVERTISEMENT · REMOVE?

Every project eventually makes you pick a config format. YAML is everywhere. JSON is older than some of your coworkers. TOML showed up more recently with its hand raised, saying “actually, I was designed for this.” All three will betray you eventually. The betrayals are just different.

Here’s a direct comparison — same config, three formats — followed by exactly when each one will make you regret your life choices.

The Same Config, Three Ways

A basic web app config: name, port, debug flag, version string, database settings, allowed origins. Nothing exotic. This is where the format differences start to show.

TOML

# App configuration
[app]
name = "my-app"
port = 3000
debug = false
version = "1.2.3"
allowed_origins = ["https://example.com", "https://api.example.com"]

[database]
host = "localhost"
port = 5432
name = "mydb"

YAML

# App configuration
app:
  name: my-app
  port: 3000
  debug: false
  version: "1.2.3"
  allowed_origins:
    - https://example.com
    - https://api.example.com

database:
  host: localhost
  port: 5432
  name: mydb

JSON

{
  "app": {
    "name": "my-app",
    "port": 3000,
    "debug": false,
    "version": "1.2.3",
    "allowed_origins": [
      "https://example.com",
      "https://api.example.com"
    ]
  },
  "database": {
    "host": "localhost",
    "port": 5432,
    "name": "mydb"
  }
}

At a Glance

FeatureTOMLYAMLJSON
Comments✅ Yes✅ Yes❌ No
Type inferenceExplicitAggressive (often wrong)Explicit
Arrays= ["a", "b"]- item or inline["a", "b"]
Trailing commasN/AN/A❌ Illegal
Deeply nested configsGets verbose fastReadable-ishVerbose but unambiguous
Spec stabilityTOML 1.0 (2021, stable)1.1 vs 1.2 parser chaosStable
Null support❌ No null type✅ Yes (~ or null)✅ Yes (null)
Common useCargo.toml, pyproject.tomlGitHub Actions, k8s, Dockerpackage.json, tsconfig.json

YAML: Most Readable Until It Isn’t

YAML looks great in demos. A flat config reads almost like prose. The trouble starts when you hit one of its edge cases — and by then your config file is already load-bearing infrastructure.

The Norway Problem

In YAML 1.1 — which most parsers still default to — these values are all booleans: y, n, yes, no, on, off, true, false. So country: NO parses as country: false. This is the real reason it’s called the Norway Problem — Norway’s ISO country code is NO. PyYAML fixed this in v6.0 (released 2022). SnakeYAML (used by a lot of Java tooling) still hasn’t fully addressed it. Check your parser before using bare no or yes in config values.

Type Inference That Guesses Wrong

Unquoted values in YAML get type-coerced. port: 8080 becomes an integer. version: 1.10 becomes the float 1.1 — mathematically equal, semantically wrong. Forget to quote a version string and you’ll spend ten minutes wondering why your app thinks it’s on v1.1 instead of v1.10. The fix is boring: quote everything that should stay a string. But YAML doesn’t force you to, so it doesn’t.

Indentation Is Load-Bearing

Tabs are illegal in YAML — not discouraged, illegal. Mix two-space and four-space indentation within a file and you get a parse error that often points to the wrong line. GitHub Actions is the sharpest edge here: a misindented run: block fails at runtime, not at parse time, because workflow runners only validate syntax, not step structure. You’ll get “unexpected value” from a CI job with no indication of which step broke, and you’ll spend 20 minutes adding debug output before realizing the problem was a two-space indent where four was expected.

If your YAML has become a mess of inconsistent indentation, the YAML Formatter will normalize it before you start debugging.

TOML: The Format That Actually Thought About Config

Tom Preston-Werner (GitHub co-founder) built TOML because he was tired of writing INI-style configs with inconsistent parsing behavior and YAML configs that surprised him. TOML 1.0 landed in January 2021 after years of revisions. It’s now the standard for Rust projects (Cargo.toml), Python packaging (pyproject.toml), and Hugo sites. The spec is stable, the parsers are consistent, and the type system does what you expect.

What It Gets Right

  • No surprise type coercions. version = "1.10" is always a string. port = 3000 is always an integer. What you write is what you get.
  • Comments work exactly like you’d expect (# to end of line), unlike JSON.
  • Flat to moderately nested configs are genuinely readable, unlike deeply nested JSON.

The Array-of-Tables Syntax

TOML’s main stumbling block is its array-of-tables syntax. If you want an array of objects — say, multiple database connections — the notation looks like this:

[[databases]]
name = "primary"
host = "db1.example.com"

[[databases]]
name = "replica"
host = "db2.example.com"

Each [[double bracket]] section is one item in the databases array. It works. It’s unambiguous. But every developer who opens a TOML file for the first time asks “is this INI?” — because it kind of looks like it. That unfamiliarity has a real cost when you’re onboarding contributors who’ve never seen TOML before.

TOML also has no null type — intentionally. If your schema uses null to mean “key is present but explicitly unset,” you’ll need to model that differently (omit the key entirely, or use a sentinel value). And deeply nested configs become verbose quickly: TOML doesn’t have YAML’s anchor/alias system for reusing subtrees, so there’s a lot of copy-paste if your config has repeated structure.

The TOML Formatter is handy when you’re trying to clean up a TOML file that’s grown organically over time.

JSON: The Devil You Know

JSON was designed for data interchange — machines talking to machines — not for humans writing config files. It ended up as a config format because every language already had a JSON parser, and that convenience won. Now we have package.json, tsconfig.json, .eslintrc.json, and approximately 40 other JSON configs in every JavaScript project, all of which you edit by hand.

No Comments. Still.

Douglas Crockford removed comments from JSON intentionally in 2012 — he worried developers would use them as parsing directives (similar to IE’s conditional comments). The internet has complained about this every day since. The workarounds people use:

  • JSONC — JSON with Comments. VS Code uses it for settings.json and launch.json. Not parseable by standard JSON parsers. Non-standard.
  • JSON5 — adds comments, trailing commas, unquoted keys, multiline strings. Has a spec and a standalone parser. Babel uses it for configs. Still not standard JSON.
  • A "_comment" key — a string field that holds your comment text. Works. Looks ridiculous. Gets into your data model.

Trailing Commas

Also illegal. Add a trailing comma after the last item in an array or object and JSON.parse throws SyntaxError: Unexpected token } — telling you there’s a problem, but not where the errant comma is. This is the number one JSON parse error in human-authored config files, and it happens because every other modern language (JavaScript arrays, Python lists, Rust enums) allows trailing commas and humans write JSON by hand with the same habits.

What JSON Gets Right

The type system is unambiguous and universal. Every JSON parser in every language agrees on what true, 1, "1", and null mean. JSON Schema is the most mature config validation option of the three — VS Code uses it to validate tsconfig.json and package.json in-editor, with inline error highlighting. When tooling writes your JSON (webpack, tsc, npm), you don’t care about readability anyway — that’s what the JSON Formatter is for.

Verdict: Pick Based on Context, Not Preference

Use JSON when tooling generates or consumes it (package.json, tsconfig, AWS configs, GitHub API responses), or when you need JSON Schema validation. Don’t fight it by writing it by hand more than necessary. The lack of comments hurts, but the ubiquity and tooling support are hard to argue with.

Use YAML when the config is primarily human-authored and relatively flat — GitHub Actions workflows, Docker Compose files, Kubernetes manifests. Quote anything that could be mistyped as a boolean or number (version strings, country codes, anything starting with a digit). Run a linter. Never use tabs. Treat type inference as a bug, not a feature.

Use TOML when you control the format choice and want no surprise type coercions. It’s the most honest of the three about what it is. If you’re starting a greenfield project and none of your tooling mandates a format, TOML is the least likely to surprise you six months in. The unfamiliarity is a one-time cost; the explicitness is permanent.

Want To enjoy an ad-free experience? Go Ad-Free Today

Install Our Extensions

Add IO tools to your favorite browser for instant access and faster searching

Add to Chrome Extension Add to Edge Extension Add to Firefox Extension Add to Opera Extension

Scoreboard Has Arrived!

Scoreboard is a fun way to keep track of your games, all data is stored in your browser. More features are coming soon!

ADVERTISEMENT · REMOVE?
ADVERTISEMENT · REMOVE?
ADVERTISEMENT · REMOVE?

News Corner w/ Tech Highlights

Get Involved

Help us continue providing valuable free tools

Buy me a coffee
ADVERTISEMENT · REMOVE?