All three describe data, but they make opposite trade-offs. Because their data models overlap, you can usually convert a copy: JSON ↔ YAML and JSON ↔ CSV both run in your browser, and nothing is uploaded. Nested objects, comments and YAML's unquoted scalars are the usual lossy bits.
The same record in all three
JSON: {"name": "Ada", "role": "admin", "active": true}
YAML: name: Ada
role: admin
active: true
CSV: name,role,active
Ada,admin,trueWhat actually differs
- Data model. JSON (RFC 8259) and YAML share objects, arrays, strings, numbers, booleans and null, YAML adds anchors, tags and multi-document files. CSV (RFC 4180) has exactly one shape: rows of untyped text fields. In CSV,
trueand01234are just characters. - Comments. YAML has them (
#); JSON and CSV do not. This alone explains YAML's dominance in config files, humans annotate configs. - Ambiguity. JSON has essentially none. YAML has famous foot-guns: unquoted
noparses as a boolean (the “Norway problem”, country codeNO), leading zeros turn into numbers. CSV's ambiguity is dialects: delimiters, quoting and encodings vary by exporting locale, European Excel emits semicolons. - Whitespace. Meaningless in JSON, structural in YAML (indentation is syntax), and significant inside CSV fields.
YAML's power features, and the price
YAML earns its config-file role with features JSON refuses to have. Anchors deduplicate repeated blocks, one definition, reused by reference:
defaults: &defaults retries: 3 timeout: 30 production: <<: *defaults # merges retries and timeout timeout: 60 # then overrides one key
Convert that YAML → JSON and the references vanish into expanded copies, which is also the fastest way to see what your anchors actually produce. The price of YAML's friendliness is implicit typing. This innocent country list:
countries: [DE, FR, NO] # parses as [DE, FR, false] in YAML 1.1 parsers version: 1.20 # the number 1.2, trailing zero gone zip: 01234 # 1234 in YAML 1.2, octal 668 in 1.1 parsers
NO is Norway to you and a boolean to a YAML 1.1 parser, the famous “Norway problem”. YAML 1.2 fixed most of this on paper, but widely deployed parsers (libyaml, PyYAML) still follow 1.1 rules. The defensive habit: quote every scalar that must stay a string.
CSV's dialect problem
RFC 4180 nails down commas, CRLF line endings and double-quote escaping ("" inside a quoted field), but real-world CSV predates the RFC by decades and Excel is its own standard. Concretely: a German or Dutch Excel exports semicolon-separated files (because the comma is the decimal separator there); Excel eats leading zeros unless a column is typed as text; and it historically needed a UTF-8 BOM at the start of the file to render accents correctly. If an “invalid” CSV crosses your desk, check the delimiter and the encoding before blaming the data, then convert it with CSV → JSON once it is comma-separated.
Choose by job, not by taste
Use JSON when programs talk to programs: API payloads, message queues, storage formats, log lines. Every language parses it natively and fast, and its strictness means a document either parses or fails loudly, check any document with the JSON Formatter & Validator.
Use YAML when humans maintain the file: Kubernetes manifests, CI workflows, docker-compose. You get comments, less punctuation noise, and multi-line strings, at the price of the typing foot-guns above. When a YAML config behaves strangely, converting it YAML → JSON shows you what the parser actually understood.
Use CSV when the data is a flat table and a spreadsheet is on either end of the pipeline: exports for analysts, imports from Excel, bulk data loads. It is the only one of the three that non-programmers open directly. Convert a spreadsheet export into objects with CSV → JSON when it needs to enter code.
Converting between them
JSON ↔ YAML is lossless for data (comments and anchors are lost in the YAML → JSON direction, since JSON cannot express them). JSON ↔ CSV is only faithful when the JSON is an array of flat objects with consistent keys, exactly the shape of a table. Round-tripping through CSV strips types: numbers and booleans come back as strings, by design, because guessing corrupts values like postal code 01234.