You’re standing up a new service. There’s a config file to write, an API contract to publish, and a one-off export of 40,000 rows from the legacy database. Each of those jobs has a different right answer, and picking the wrong one is how teams end up with Kubernetes manifests that fail to parse because someone used tabs, or a CSV that breaks because a product description contained a comma. The yaml vs json question alone has started enough Slack arguments to deserve a writeup. Add CSV for tabular data and you have the three formats that cover most of what a developer reaches for in a week.
JSON — strict, ubiquitous, no comments
JSON is defined in RFC 8259. It’s the lingua franca of HTTP APIs because the grammar is small and unambiguous: objects, arrays, strings, numbers, booleans, null. Every language has a parser, and they all agree on the output for any valid input. That strictness is the feature. A missing comma, a trailing comma, or a single-quote string is a parse error, not a guess.
The trade-off is that JSON is hostile to humans editing it by hand:
{
"port": 8080,
"hosts": ["api.example.com", "cdn.example.com"],
"timeout_ms": 30000
}
No comments allowed. If you want to leave a note for the next reader, you either ship a config.example.json alongside the real file, or invent a _comment field that every consumer silently ignores. Numbers are doubles in most parsers, so 10000000000000001 round-trips to 10000000000000000 and you don’t find out until the invoice lands. For a machine-produced API payload those problems don’t exist, which is why JSON wins there. For a config file a human writes, it’s a tax. When you’re staring at a 600-line API response, the JSON Formatter exists for that moment.
YAML — human-friendly, until it isn’t
YAML 1.2 is the format of choice when humans write and edit. Comments work. Strings don’t need quotes in the common case. Nesting reads like an outline. A config that would be a wall of braces in JSON becomes something you can scan:
# Service configuration
port: 8080
hosts:
- api.example.com
- cdn.example.com
timeout_ms: 30000
The cost of that readability is two traps that bite everyone once.
The first is the indentation trap. YAML uses whitespace for structure, and the rules are precise in a way that looks forgiving. Mix tabs and spaces and the parser rejects it. Indent a list item two spaces less than its parent and you’ve quietly attached it to the wrong node:
production:
web:
replicas: 3
rolling_update: true # one space short — parse error or wrong tree
That third line has three leading spaces where the parser expects a multiple of the established indent. Some parsers throw; others silently nest rolling_update somewhere you didn’t intend. Helm, Ansible, GitHub Actions, Docker Compose — every YAML-driven tool has a troubleshooting page whose first item is “check your indentation.” If you inherit a file that won’t parse, the YAML Formatter reflows it and shows where the structure breaks.
The second trap is implicit type coercion. YAML 1.1 parsers (and several 1.2 parsers in compatibility mode) treat unquoted yes, no, on, off, true, false as booleans. The classic “Norway problem”: a list of country codes that includes Norway (NO) silently coerces that one entry to the boolean false, because YAML 1.1 treats no, NO, n, and N as false. Norway’s code vanishes into a boolean and your data type-checks break. The fix is to quote strings that look like anything else, or to use a YAML 1.2 parser that’s dropped the 1.1 booleans. You will forget this once.
CSV — tabular, simple, with a quoting problem
CSV is defined in RFC 4180, though “CSV” in the wild means “whatever Excel produced.” It’s the right format when your data is genuinely a table — rows of the same shape, columns identified by header. Database exports, spreadsheet round-trips, log aggregates. Every analytics tool on earth ingests it.
The catch is that commas are legal inside values, so values containing commas (or newlines, or quotes) must be wrapped in double quotes, and quotes inside those values must be doubled:
id,name,note
1,Widget,"Says ""hello"", loudly"
2,Gadget,Backordered
That’s a tractable grammar, and a real CSV library handles it. Hand-rolled line.split(",") does not. Every team has a story about the production CSV parser that broke the day a user typed a comma into a free-text field. If you’re moving between formats, the CSV/JSON converter does the row-to-object translation without you having to remember the quoting rules.
Which format when
| Format | Config file | API payload | Tabular data | Human-edited | Machine-only |
|---|---|---|---|---|---|
| JSON | Workable, no comments | Best choice | Awkward | Painful | Best choice |
| YAML | Best choice | Rare (coercion, parse cost) | Wrong tool | Best choice | Avoid |
| CSV | Never | Never | Best choice | OK for tables | Fine for bulk export |
The columns that matter most are human-edited versus machine-only. JSON is the safe default the moment a program is producing the data and another program is consuming it. YAML earns its keep when a person opens the file in a text editor and changes a value. CSV is correct only when the data is genuinely a table.
Converting between them
Most pipelines touch all three. A Kubernetes manifest in YAML gets POSTed as JSON to the API server. A database export in CSV becomes a JSON document for a search index. The conversions are mechanical but lossy in specific ways.
YAML to JSON is usually clean — YAML is a superset of JSON’s data model, so scalars, lists, and maps map one-to-one, and you only lose comments. JSON to YAML works, but the output is rarely the file a human would have written; block style appears where flow style is clearer, and anchors get expanded into duplicated subtrees. CSV to JSON is one object per row, with the header row supplying keys. JSON to CSV only works if every object has the same shape — nesting and arrays either get flattened with dot-paths or rejected, and this is the conversion most likely to lose data, so check the output.
Round-trips aren’t guaranteed. JSON → YAML → JSON is usually lossless; CSV → JSON → CSV can reorder columns if your converter isn’t careful about header order.
FAQ
Can YAML files include JSON directly?
Yes. YAML 1.2 is a superset of JSON, so any valid JSON document is also a valid YAML document. You can paste a JSON block into a YAML file unchanged, and most parsers will read it. The reverse isn’t true — YAML’s comments, anchors, and unquoted strings aren’t valid JSON.
Why does JSON not allow comments?
RFC 8259 explicitly excludes them. Douglas Crockford’s stated reason was to prevent parsers from diverging on comment syntax, which had fragmented earlier config formats. The result is that JSON is only ever data, never documentation. If you need comments alongside JSON, the common workaround is a separate schema or a JSONC variant, which some tools support and the spec does not.
Is CSV ever the wrong choice for tabular data?
Yes, when cells contain newlines or when the table has heterogeneous row shapes. Parquet, TSV, or a database dump handle those cases better. CSV is unbeatable for the common case — uniform rows, simple scalars, broad tool support — and painful the moment a value carries structure of its own.
The decision rule
JSON for machine-to-machine. YAML for human-edited config, with comments and quotes around anything that could be mistaken for a boolean. CSV for tables. If you remember the Norway problem, never use tabs in a YAML file, and quote your commas in CSV, you’ll avoid the three mistakes that account for most of the format-related incidents I’ve seen shipped to production. Format choice is a small decision made early that compounds; pick the one whose failure mode you can live with.