JSON vs YAML vs TOML: when to use what.

Three formats for roughly the same job, three completely different philosophies. Here's how to pick without ending up in yet another pull-request comment thread that nobody wins — and the gotchas nobody mentions until you've hit them.

There's a pull request somewhere right now where someone proposed switching a config file from YAML to TOML, or from JSON to YAML, and the comments have spiraled into a debate about developer ergonomics, schema validation, and what the parent company's other teams use. The PR has seventeen comments. The author has stopped responding. The config file in question is eighteen lines long.

This happens because nobody agreed on when to use which. The honest answer is that JSON, YAML, and TOML were designed for different things, and most arguments about them are arguments about taste dressed up as arguments about engineering. Let's sort out which is actually which.

The three-sided argument nobody's winning.

Here's the pattern. Someone introduces a new format to a codebase. A colleague asks why not the existing one. The answer is some mix of this format is cleaner, the community uses this, it has comments, and it's the standard for X. All of these are true. None of them are actually the reason — the actual reason is almost always a mix of what the person has used before and what feels right to edit by hand.

That's fine. Taste is real. But it means the technical arguments usually come after the decision, which is backwards. If you sort the design goals out first, the right format for a given job becomes obvious and the argument stops being interesting.

The three formats, honestly.

Each format was born in response to a specific frustration. Understanding what each was rejecting tells you what it's good at.

JSON

Douglas Crockford extracted JSON from JavaScript object literal syntax around 2001, with a deliberately tiny spec. The goal was a data interchange format machines could produce, transmit, and parse fast. Crockford famously removed comments from the spec on purpose — he said people were using them to hold parsing directives, and he didn't want that.

{
  "port": 8080,
  "workers": 4,
  "features": ["rate-limit", "gzip"]
}

It's the format every HTTP API speaks. Every language has a parser. It's the closest thing the industry has to a shared baseline.

YAML

YAML started in 2001 as "Yet Another Markup Language" before being retroactively renamed "YAML Ain't Markup Language" — both names tell you the same thing, which is that the original goal was not being XML. Structure comes from indentation and whitespace, the way Python does it. Comments are allowed. The syntax is optimized for humans to type and read.

port: 8080
workers: 4
features:
  - rate-limit
  - gzip

This is what Kubernetes manifests look like. What Ansible playbooks look like. What GitHub Actions workflows look like. That's not a coincidence — it's what YAML was built for.

TOML

TOML arrived in 2013, designed by Tom Preston-Werner (co-founder of GitHub) because YAML's syntax surprised him too often and INI files were too weak. The name stands for "Tom's Obvious, Minimal Language." The design goal is that you can edit it without reading the spec first.

port = 8080
workers = 4
features = ["rate-limit", "gzip"]

Rust's Cargo.toml made TOML mainstream. Python's pyproject.toml made it ubiquitous. It's the fastest-growing of the three, mostly because the friction of editing it is genuinely low.

JSON: the machine's format.

What it's good at. Small, strict, universally supported. Serializes fast. Parses fast. Streams naturally. Every language from Rust to COBOL has a battle-tested parser. If two programs need to exchange data and neither of them is going to be read by a human, JSON is almost always the right answer.

What it's bad at. No comments. No trailing commas. No multi-line strings without embedded \n escapes. No date type — dates are just strings you hope everyone agreed to format the same way. Integers officially have no precision limit, but JavaScript's JSON.parse clamps at 2^53, which silently corrupts large IDs from languages with 64-bit ints. Editing a nested JSON config by hand is unpleasant enough that people invent editors for it.

When to reach for it. API request and response bodies. Wire protocol between services. Serialized data stored in a database (Postgres jsonb, MongoDB documents). Log output that another program will parse. Anywhere a machine is both writing and reading.

When not to. Anything a human will edit regularly. Any config file more than about fifteen lines. Anything needing comments — the workaround of adding a "_comment": "..." field works but is depressing.

YAML: the human's format.

What it's good at. Deep nesting without visual clutter. Comments. Multi-line strings (in nine different ways, which is foreshadowing). Anchors and aliases for reusing chunks of config — the feature that actually justifies using YAML for long Kubernetes manifests where the same pod spec appears four times. A single well-structured YAML file is genuinely easier to read than the equivalent JSON.

What it's bad at. The spec is enormous and most parsers implement a subset. Indentation is meaningful and tabs will silently break everything. The infamous Norway problem: YAML 1.1 interpreted NO, no, off, yes, and on as booleans, which meant a country code column full of NO for Norway got parsed as false. YAML 1.2 fixed this in 2009; many popular parsers still default to 1.1 in 2026. The format that was built to reduce surprise produces more surprise than any other format you'll touch.

When to reach for it. Kubernetes, Docker Compose, GitLab CI, GitHub Actions, Ansible — because the tool expects it and the community has decades of collective pattern-recognition for making these files not-weird. Long config with deep nesting where repetition would hurt. Anywhere anchors and aliases genuinely reduce noise.

When not to. Small config files. Anything a less-experienced engineer needs to edit (the indentation rules are not obvious on first contact). Data interchange between services. API responses. Anywhere the spec's ambiguity will be read as a bug.

TOML: the config format.

What it's good at. Obvious syntax. key = value at the top level, [section] for grouping. No indentation sensitivity. Explicit types — strings have quotes, numbers don't, booleans are true/false, dates are first-class. A strict spec that leaves no room for parser disagreement. Reading a TOML file you've never seen before is a solved problem.

What it's bad at. Deeply nested data gets awkward fast. The [[array-of-tables]] syntax for lists of objects is powerful but surprising on first contact. The ecosystem is smaller than JSON's or YAML's — you won't find a TOML parser in every obscure language. And while the strict typing is usually a virtue, it means you can't fudge it the way you can in YAML when you just want a quick draft.

When to reach for it. Project configuration (Cargo.toml, pyproject.toml, almost any CLI tool's config). Simple-to-moderate structure. Files that humans edit but machines also consume. Anywhere you want to hand someone a config file and not watch them read the docs.

When not to. Data interchange (nobody else's API speaks TOML). Complex nested structures where the array-of-tables syntax stops being readable. Machine-generated output where JSON's ubiquity wins.

A decision matrix.

If you want to skip the reasoning and just pick:

SituationPickWhy
API request or response bodyJSONUniversal, strict, machine-oriented
Kubernetes manifestYAMLThe community expects it; anchors help
Rust project configTOMLCargo requires it
Python project configTOMLpyproject.toml is now the standard
User-editable CLI tool configTOMLHardest to get wrong, easiest to teach
Multi-environment application configYAMLAnchors let you share common base
Log output a program will re-parseJSONFast to serialize, fast to parse, no ambiguity
CI pipeline definitionYAMLEvery CI platform settled on it
Database column holding nested dataJSONNative indexing in Postgres, Mongo, MySQL
Writing docs you'll version-controlnoneUse Markdown or plain text

The pattern: JSON when machines are the primary audience, YAML when deep-nested humans are, TOML when flat humans are. That's the whole rule, compressed.

The things nobody tells you.

Five gotchas that come up regularly enough to be worth knowing in advance.

The Norway problem is still live

A column called country_code in YAML 1.1 interpreted the value NO as boolean false. Same for yes, off, on. YAML 1.2 fixed this in 2009, but in 2026, many widely-used parsers (including some versions of PyYAML and a long tail of JavaScript libraries) still default to 1.1 behavior unless explicitly configured otherwise. Always quote your country codes, always quote "yes" and "no" if they're strings, and probably always quote your strings in YAML as a lifestyle choice.

JSON has no 64-bit integer guarantee

The JSON spec says numbers can be any precision. JavaScript's JSON.parse says otherwise: anything over 2^53 (about 9 quadrillion) gets silently rounded. If your API returns 64-bit user IDs from a Go backend and a JavaScript client parses them, you will lose precision. Workaround: serialize large IDs as strings. Many languages' JSON libraries have a "parse big numbers as strings" option; turn it on.

TOML dates are actually a type

Unlike JSON (where "2024-10-08T14:11:35Z" is just a string) or YAML (where the parsing depends on your parser's mood), TOML defines 2024-10-08T14:11:35Z as a proper datetime value, with rules. Offset datetime, local datetime, local date, local time — all first-class. If your config has real dates, TOML handles them correctly without stringly-typed workarounds.

YAML's multi-line strings have nine syntaxes

Not exaggerating. Block scalars with |, >, |-, |+, >-, >+, plus folded, literal, and flow styles. They handle trailing newlines, leading indentation, and line-folding in different ways. Every YAML user has at least one story about a | where they meant >. The cheatsheet: | preserves newlines, > folds them into spaces, add - to strip the final newline, add + to keep all trailing newlines.

"Human-readable" means three different things

JSON is human-readable in the sense that it's text, not binary. YAML is human-readable in the sense that a non-programmer could plausibly edit it. TOML is human-readable in the sense that a programmer who has never seen the file before can edit it without breaking it. These are not the same claim. When someone says "format X is human-readable," ask which human, at what skill level, doing what.

If you just want to convert one.

The theoretical argument is one thing. The practical moment is: the config ships as JSON, you wish it were TOML, or the third-party tool writes YAML, you need to merge it into your JSON pipeline. Conversion between these formats is lossy in specific, predictable ways — datetimes, comments, and YAML anchors don't round-trip cleanly — but for most real-world configs, automated conversion is safe and saves a half-hour of retyping.

All nine formats, in seventy-two directions.

Paste JSON, YAML, TOML, CSV, TSV, XML, Markdown, HTML, or SQL — get the other eight. The converter runs entirely in your browser, so configs never leave your device. Every conversion is bidirectional.

Open Format Flipper

Made with love by a very serious person pretending not to be. Tooly McToolface is a workshop of free, client-side web tools. If you liked this, the dates guide covers the same territory for timestamps — and the "free isn't free" essay is why the tools here don't track you.