JSON, YAML and CSV can all describe the same information. Choosing between them is usually decided by what the receiving system expects, but when the choice is genuinely yours, the differences that matter are not the ones in the feature comparison tables. They are the failure modes.
The short version
- JSON — machine to machine. Unambiguous, universally supported, tedious to hand-edit.
- YAML — human-edited configuration. Readable and diffable, with genuinely dangerous type coercion.
- CSV — tabular data going to a spreadsheet. Trivially simple until a value contains a comma.
JSON: boring, and that is the point
JSON's strength is that it has almost no features. Objects, arrays, strings, numbers, booleans, null. No comments, no references, no dates, no type system. That minimalism is why every language parses it identically and why there is essentially never an argument about what a JSON document means.
Its weaknesses follow from the same minimalism:
- No comments. This is the single most requested feature and it is never coming. Configuration in JSON accumulates a
"_comment"key or an adjacent README. - Trailing commas are illegal. The most common syntax error by a wide margin, because they are legal in JavaScript.
- Numbers are underspecified. The specification does not mandate a precision. In practice most parsers use IEEE 754 doubles, so integers above 253 lose accuracy silently — a real problem for 64-bit database identifiers, and the reason mature APIs send those as strings.
- No date type. Everyone uses ISO 8601 strings, which works, but it is a convention rather than a guarantee.
YAML: readable, with a loaded footgun
YAML exists because editing JSON by hand is miserable. It gives you comments, multi-line strings that stay readable, and a structure that diffs cleanly in a pull request. Kubernetes, Helm, GitHub Actions, Compose and Ansible all chose it for exactly those reasons.
The cost is that YAML tries to guess what you meant.
The Norway problem
YAML 1.1 treats yes, no, on,
off, true and false as booleans. So:
countries:
- GB
- NO
- FR
parses as ["GB", false, "FR"]. The country code for Norway
becomes boolean false. This has a name because it has bitten enough people
to earn one.
Version numbers lose digits
version: 1.10 is a number, and the number 1.10 is 1.1. Your
carefully pinned version silently becomes a different one. Meanwhile
version: 1.2.3 is a string, because it is not a valid number —
so the same field changes type between releases.
Tabs are illegal
YAML forbids tab characters for indentation outright. An editor configured to insert tabs produces a file that looks perfectly aligned and fails to parse, with an error pointing somewhere unhelpful. The character is invisible, so this can cost an afternoon.
Duplicate keys silently win
Most parsers accept a duplicated key and let the last one take effect. In a 400-line values file assembled by several people, the symptom is a setting that visibly exists in the file and has no effect whatsoever.
The defence against all of this is quoting. If a value is meant to be a
string, quote it. country: "NO" and
version: "1.10" mean exactly what they look like. The
converter and
Helm formatter here quote
anything that would be re-read as the wrong type, precisely because this
class of bug is so hard to spot by eye.
CSV: simpler than it looks, harder than it seems
CSV is the format you reach for when the destination is a spreadsheet and the recipient does not read JSON. It has one real specification, RFC 4180, which is short and widely half-implemented.
Quoting is the whole problem
A field containing a comma, a double quote or a newline must be wrapped in double quotes, and internal double quotes must be doubled:
id,name,note
1,"Hopper, Grace","She said ""compile"""
2,Turing,"A note
across two lines"
Get this wrong and nothing errors. A row quietly splits into two, or two
columns merge into one, and the corruption is discovered weeks later by
someone reconciling totals. Every hand-rolled split(",") parser
is broken on real data; the only question is whether anyone has noticed yet.
It has no types and no nesting
Everything is text. Whether 007 is a string or the number 7 is
decided by whatever reads the file — and Excel will decide it is 7, deleting
your leading zeros. Nesting does not exist at all, so structured data has to
be flattened into dotted column names or given up on.
Excel will reformat your data
Opening a CSV in Excel applies aggressive type inference. Product codes lose
leading zeros, values like 1-2 become dates, and long numeric
identifiers switch to scientific notation. This is Excel's import behaviour,
not something the file can prevent. If it matters, import through
Data → From Text and set column types explicitly.
Choosing
| Situation | Use |
|---|---|
| API request or response | JSON |
| Configuration a human edits and reviews | YAML |
| Data going to someone who will open it in a spreadsheet | CSV |
| Anything with deep nesting | JSON or YAML, never CSV |
| Large volumes of uniform records | CSV, or a real columnar format |
| Data that must round-trip exactly | JSON, with big numbers as strings |
Converting without losing anything
Conversions are lossy in predictable directions, and knowing which is most of the battle:
- YAML to JSON loses comments. JSON has nowhere to put them.
- JSON to YAML is safe if ambiguous scalars are quoted. If they are not, you have introduced the Norway problem.
- JSON to CSV loses nesting and types. Flattening to dotted columns keeps it usable; anything deeper than that needs a different target.
- CSV to JSON has to guess types. A good converter leaves anything ambiguous — leading zeros, oversized integers — as text.
The JSON/YAML and JSON/CSV converters here handle these cases explicitly, and both run entirely in your browser — which matters more than it sounds, because configuration files are exactly the kind of thing that should not be pasted into someone else's server.