JSON vs YAML vs CSV: choosing a data format
Sooner or later, every project has to write structured data down: an API response, a config file, a spreadsheet export. JSON, YAML and CSV are the three plain-text formats you'll actually meet, and they aren't really competitors — they're three different jobs. Pick the right one and nobody ever thinks about it again; pick the wrong one and you inherit quoting bugs, boolean surprises and Excel-mangled ZIP codes. Here's what each format is genuinely for, where each one bites, and how to move data between them without losing anything.
Three formats, three jobs
JSON is the interchange format — the thing programs say to each other. It's what APIs return, what browsers and servers trade, what most databases will happily ingest: {"name":"Ada","admin":true}. It is compact, strict, and supported natively by effectively every language.
YAML is the configuration format — the thing humans write for programs. CI pipelines, Kubernetes manifests, docker-compose files. It trades brackets and quotes for indentation and readability, and it allows comments, which config files desperately need. Since version 1.2, YAML is formally a superset of JSON: any JSON document is valid YAML.
CSV is the table format — rows and columns, one record per line, values separated by commas. It's the lingua franca of spreadsheets, exports and data handoffs: name,age then Ada,36. It cannot nest and it doesn't want to.
A rule of thumb before the details: JSON is for machines talking to machines, YAML is for humans talking to machines, and CSV is for tables traveling between tools.
The comparison at a glance
| JSON | YAML | CSV | |
|---|---|---|---|
| Comments | None | Yes, with # | None |
| Data types | Strings, numbers, booleans, null, arrays, objects | All of JSON's, plus more via tags | None — every cell is text |
| Nesting | Yes | Yes | No — flat rows only |
| Hand-editing | Tolerable | Pleasant, until indentation bites | Only sensible in a spreadsheet |
| Standard | Strict and tiny (RFC 8259) | Large spec; 1.1 vs 1.2 differences | RFC 4180 exists; tools ignore it freely |
| Classic gotcha | Trailing commas, no comments | no becomes false | Excel eats leading zeros and dates |
JSON: strict on purpose
JSON's grammar fits on a business card, and its strictness is a feature — there's essentially one way to write a value, so parsers everywhere agree. The strictness is also where hand-editors get hurt. There are no comments, by deliberate design, which is the main reason JSON makes a poor config format. Trailing commas are invalid — [1, 2, 3,] is a syntax error, and it's the most common way a hand-edited JSON file breaks. Keys must be double-quoted strings: {name: "Ada"} and {'name': 'Ada'} are both illegal, even though every JavaScript programmer's fingers want to type them.
One subtler edge: JSON numbers are typically parsed into 64-bit floats, which hold integers exactly only up to 253 − 1 (9,007,199,254,740,991). Databases and platforms that issue larger 64-bit IDs send them as strings for exactly this reason — if you've ever seen an ID's last digits silently turn to zeros, this was the culprit. The formal grammar lives in RFC 8259, one of the shortest specs you'll ever read.
Fix it fast: the JSON formatter & validator pretty-prints, minifies and pinpoints syntax errors — in your browser, so even sensitive payloads stay on your machine.
YAML: friendly until it isn't
YAML reads the way people write: no braces, optional quotes, real comments. The cost is that whitespace is the syntax. Indentation defines structure, tabs are forbidden outright, and one wrong indent level often produces a file that parses successfully into the wrong shape — no error, just a config that quietly means something else.
Then there's the infamous boolean surprise, known in the trade as the Norway problem. Under YAML 1.1 rules, the unquoted scalars yes, no, on, off, y and n all parse as booleans — so a list of country codes turns Norway's NO into false. YAML 1.2 fixed this back in 2009, recognizing only true and false, but plenty of widely used parsers still apply 1.1 behavior, so the gotcha remains alive. Unquoted version numbers misfire the same way: version: 3.10 parses as the float 3.1, and under 1.1 rules a value like 22:22 is base-60 arithmetic — it parses as 1342. The practical rule: quote any string that could be mistaken for something else, and when in doubt, quote everything. The spec itself lives at yaml.org — it's enormous, which is its own warning: anchors, aliases and multi-document streams exist, and most config files are better off pretending they don't.
CSV: the standard that isn't
CSV looks like the easy one — values, commas, newlines, done. In reality it's the least standardized format you'll ever touch. RFC 4180 documents common practice, but it's descriptive, not enforced, and tools diverge on every axis: the delimiter (locales that use a decimal comma often export with semicolons), the encoding (Excel historically wants a UTF-8 byte-order mark, or accented characters scramble), quoting (a field containing a comma, quote or newline gets wrapped in double quotes, with embedded quotes doubled — the detail every hand-rolled parser gets wrong), and even line endings.
And because every cell is just text, the receiving program guesses at types — which is how Excel earns its reputation. It strips leading zeros (ZIP code 02134 becomes 2134), rewrites long digit strings in scientific notation, and converts anything date-shaped into a date. That last habit is famously why geneticists renamed human genes like SEPT1 and MARCH1 — Excel kept turning them into calendar entries. When someone hands you a CSV, ask three questions before parsing: what's the delimiter, what's the encoding, and is the first row headers or data?
Converting between them without losing data
Conversion is where the formats' different shapes stop being abstract.
- JSON → CSV only works cleanly for a flat array of same-shaped objects — which is exactly a table. Anything nested has to be flattened first (an
address.citycolumn) or serialized into a single cell, and arrays inside records have no natural column form at all. If your JSON is deeply nested, the honest answer is that CSV is the wrong target. - CSV → JSON has the opposite problem: every value arrives as a string.
"42","true"and""are what you get; deciding whether they should become42,trueandnullis a judgment call, not a mechanical step — remember that ZIP codes and phone numbers must stay strings precisely so their leading zeros survive. - YAML ⇄ JSON is the cleanest pair, since YAML 1.2 is a superset of JSON. JSON to YAML is essentially lossless; coming back, anchors get expanded and exotic YAML types (dates, non-string keys) need care.
Convert locally: the CSV ⇄ JSON converter treats the first row as headers and handles quoted fields correctly, and the YAML ⇄ JSON converter translates configs in both directions — both entirely in your browser, safe for data you'd rather not upload.
Which one should you pick?
- Building an API or passing data between services? JSON, without much debate. Strictness and universal parsing are exactly what interchange needs; YAML's flexibility is a liability here.
- A config file humans will edit? YAML if it needs comments and structure — with strings quoted defensively. If the config is machine-generated and machine-read, use JSON and skip YAML's ambiguity entirely.
- Tabular data headed for a spreadsheet, an analyst or a bulk import? CSV. It's the only one of the three that Excel, Google Sheets and every database's import tool all accept directly.
- Deeply nested data that humans must also edit often? That combination is the warning sign — consider splitting the file or rethinking the structure before blaming the format.
The honest limitation: sometimes none of the three is right. They're all plain text, so they're all poor homes for large datasets or binary content — that's what databases and purpose-built binary formats are for. And in practice the most common correct answer is the boring one: the format the other side already speaks. A CSV nobody can open is worse than a merely inelegant JSON file.
One place all three meet: a JSON Web Token's payload is just Base64URL-encoded JSON, readable by anyone who holds the token — our JWT guide takes one apart. And if your data has timestamps in it (it does), the timestamps guide covers the other classic conversion trap.
Related: JWTs explained: what's inside a token and how to debug it · Unix timestamps and time zones, demystified · Why Toolkit runs entirely in your browser