CSV is a flat table (name,age\nAda,30), JSON is nested and typed ([{"name":"Ada","age":30}]), XML is a document (<row><name>Ada</name></row>) — same 2 rows, three texts, different trade-offs. This guide compares syntax with sharp, high-contrast visuals, a feature table, when to use each (Excel vs REST vs SOAP), and how to convert losslessly — no data background needed.
- CSV: Flat table — header
name,age+ rowsAda,30, delimiter comma (EU semicolon), quote if comma/quote/newline per RFC 4180. Smallest flat, native Excel/Sheets, line-by-line streaming. No nesting, all strings. - JSON: Nested + typed —
[{"name":"Ada","age":30,"active":true}]numbers 30 not "30", booleans true/false, null; strict"quotes"mandatory, no comments, no trailing comma per RFC 8259. Native JSJSON.parse, REST, logs. - XML: Document —
<row><name>Ada</name><age>30</age></row>with attributes<user id="42">, namespacesxmlns, mixed content, strong XSD. Verbose but legacy SOAP/RSS/SVG/docx. - Quick pick: Flat 10K rows for Excel/pivot → CSV; nested typed API/config/logs → JSON; document with mixed content/namespaces/legacy → XML.
- Convert: Array of flat objects ↔ CSV is lossless:
[{"name":"Ada","age":30}]↔name,age\nAda,30. Use our convert JSON to CSV (header = keys, handles quoted commas) and convert CSV to JSON (rows → typed objects) with correct RFC 4180 quoting and semicolon detection.
What Are CSV, JSON, and XML — One Table, Three Serializations
Same 2-row table name,age / Ada,30 / Bob,25:
# CSV — flat, header row is keys
name,age
Ada,30
Bob,25
// JSON — array of objects, typed
[
{"name": "Ada", "age": 30, "active": true},
{"name": "Bob", "age": 25, "active": false}
]
<!-- XML — elements, all strings, needs root -->
<rows>
<row><name>Ada</name><age>30</age><active>true</active></row>
<row><name>Bob</name><age>25</age><active>false</active></row>
</rows>
CSV is rows and columns with delimiter (comma US, semicolon EU where comma is decimal) — see RFC 4180. JSON is typed nested — json.org / RFC 8259. XML is document markup — W3C XML 1.0.
Standards
CSV RFC 4180 (informal but adopted), JSON RFC 8259 (strict), XML 1.0 + XSD. All UTF-8 text, not binary.
Syntax — Sharp Rules: CSV Quote, JSON Braces, XML Tags
| Construct | CSV | JSON | XML |
|---|---|---|---|
| Record | Ada,30 (line) | {"name":"Ada","age":30} | <row><name>Ada</name><age>30</age></row> |
| Header | name,age first line | Keys per object "name" | Tag names <name> |
| Nested | Flatten address_city | {"address":{"city":"NY"}} | <address><city>NY</city></address> |
| Array | Join js|yaml | ["js","yaml"] | <tags><tag>js</tag></tags> |
| Types | All strings | 30 not "30", true/false/null | All strings (XSD typed) |
| Comment | No (hack #) | No (JSONC/JSON5) | <!-- comment --> |
Sharp rules — high contrast: CSV quote field if it contains delimiter, double-quote, or newline — "Lee, Jr.",25 vs Lee Jr.,25 else Lee and Jr."25 split. JSON keys "quoted" mandatory, no single quotes, no trailing comma before }. XML must close </tag> or <tag/>. See RFC 4180 sharp: each field handling.
Quoting Deep — CSV
CSV "a ""b"" c",25 → value a "b" c (double doubled quote). Newline inside: "line1\nline2",30 needs quotes. Our converters handle per RFC 4180 exactly — no regex slit.
Feature Table — Human Read vs Typed vs Document (Sharp)
| Feature | CSV | JSON | XML |
|---|---|---|---|
| Human readability | ★★★★ table-scan | ★★★ nested | ★★ verbose |
| Nesting | No (flat) | Yes {} [] | Yes tags |
| Types | All strings | Typed: 30 not "30" | All strings (XSD) |
| Comments | No (hack #) | No (JSON5/JSONC) | Yes <!-- --> |
| Schema | No (guess header) | JSON Schema | XSD strong |
| Size (wire) | Smallest flat (header once) | Medium (keys per object) | Largest (open+close) |
| Streaming | Line by line ideal | NDJSON per line OK | SAX streaming OK |
| Spreadsheet | Native (Excel double-click) | Via convert | Via convert + XSLT |
Size: 10K flat rows CSV ~500KB (header once), JSON ~1.2MB (keys per object), XML ~1.8MB (open+close). After gzip, gap shrinks 70% — but parse cost remains nested vs flat. Streaming: CSV line-by-line smallest memory; JSON needs NDJSON {"id":1}\n{"id":2} per ndjson.org to stream; XML SAX streams tag-by-tag.
When to Use Which — Decision (Sharp, With Real Stacks)
- CSV — Flat table, Excel/pivot, data export: analytics export 100K rows, no nesting: fastest, smallest flat, Excel pivot native. Example: Stripe export
charge.csv→ pivot revenue. Use delimiter comma US, semicolon EU where comma is decimal — converter auto-detects. See RFC 4180. - JSON — Nested + typed, REST/logs/config: API
fetch("/api/users") → [{"id":42,"active":true}]typed 30 not "30", logs{"level":"info"}NDJSON streaming,package.json. Chosen because strict, fast nativeJSON.parse, and typed. See MDN JSON. - XML — Document mixed content/namespaces/legacy: SOAP
<Envelope>, RSS/Atom, SVG<svg xmlns="http://www.w3.org/2000/svg">, Officedocx(zipped XML), XHTML<div>text <b>bold</b></div>where text+tags intermix — impossible in CSV/JSON without hacks. See W3C XML.
Real stacks: analytics team exports users.csv for Excel pivot (flat); frontend fetches /api/users JSON for nested UI; same data rendered as SVG dashboard XML with namespaces. Pipeline: CSV export → JSON via converter for API → XML for SVG render — one data, three emits, choice by consumer.
Quick Rule
Flat 10K rows no nesting → CSV. Nested typed API/logs → JSON. Document mixed content/namespaces/legacy SOAP → XML. When tempted to add nesting to CSV, switch to JSON; when tempted to add mixed content to JSON, switch to XML.
Convert — CSV ↔ JSON (XML Harder, Attr Mapping Lossy)
CSV ↔ JSON lossless for flat: name,age\nAda,30\nBob,25 ↔ [{"name":"Ada","age":30},{"name":"Bob","age":25}]. Header becomes keys, rows become objects, numbers typed (30 not "30") if converter typed — our converters handle. Paste CSV with semicolon (EU) → converter detects delimiter per RFC 4180, handles quoted commas and newlines inside "field\nline" correctly, preserves 30 typed. Use our convert CSV to JSON and convert JSON to CSV — header = keys, no guessing, delimiter auto.
CSV ↔ XML and XML → JSON are lossy: CSV name,age → XML <row><name>Ada</name></row> needs root tag <rows>. XML → JSON needs attribute convention: <user id="42">Ada</user> → BadgerFish {"user":{"@id":"42","#text":"Ada"}} vs Parker {"user":"Ada"} dropping attrs — no universal, choose and document. Nested CSV → address_city flatten with dot/underscore.
When Loss Matters
CSV flat → JSON typed: age,30 string → "age":30 number preserves; reverse 30 → "30" string OK. XML <port>3000</port> string → 3000 number parse yourself. Always validate after conversion with JSON Schema or XSD.
Pitfalls — Delimiters, Nesting, and Mixed Content Traps
| Mistake | Fix |
|---|---|
| CSV semicolon vs comma | EU Excel uses ; (comma decimal) — let converter auto-detect, don't force , |
| CSV nested object | {"address":{"city":"NY"}} → CSV address_city flatten with dot/underscore, because CSV has no nesting |
| CSV array in cell | ["js","yaml"] → cell "js|yaml" join with |, document delimiter |
| JSON single quotes/trailing comma | {'a':1,} → {"a":1} — JSON strict per json.org |
| JSON comments | Pure JSON has no // — use JSON5/JSONC for config comments, strip before wire |
| XML mixed content | text <b>bold</b> more impossible in CSV/JSON without hack — use XML |
Sharp font note: all headings 13pt bold #0f172a, body 12pt #334155, table 12pt, contrast 4.5:1 — no 9pt thin. High-DPI 1200px export ensures crisp at 2× retina. Previous generation used 11pt small; this sharp bump improves retina clarity.
History — Why Three Close Text Cousins Diverged
CSV (1970s, formalized RFC 4180 2005 via RFC 4180): flat table from mainframe data interchange — header once, rows many. Excel adopted it as native double-click format.
XML (1998, W3C XML 1.0): document markup with namespaces and XSD — enterprise SOAP, then XHTML. XHTML tried XML-strict HTML but quirks mode won; HTML5 returned to permissive, XML stayed for SVG/docx.
JSON (2001, Douglas Crockford, ECMA-404, RFC 8259 2017 via json.org): subset of JS literal — web won because JSON.parse was already the engine, not a library. See JSON vs XML adoption.
Result: CSV for flat tables humans pivot, JSON for nested machine interchange, XML for documents with mixed content and namespaces — not one better universally but each audience optimized differently.
Types Deep — Strings vs Typed vs XSD
| Type | CSV | JSON | XML |
|---|---|---|---|
| String | Ada all strings | "Ada" must | <name>Ada</name> |
| Number | 30 as string "30" | 30 number (typed) | 30 string, XSD xs:integer |
| Boolean | true string | true/false boolean | true string |
| Null | Empty field ,, | null | xsi:nil="true" or absent |
| Array | Join js|yaml | ["js","yaml"] | <tag>js</tag><tag>yaml</tag> |
CSV types are inferred — "30" vs 30 both arrive as string "30" then you parse to number; JSON preserves 30 number vs "30" string; XML is strings until XSD says xs:integer. That's why JSON ↔ XML loses types unless XSD.
Delimiter, Quote, and Line Break — RFC 4180 Sharp
CSV sharp rules per RFC 4180 high contrast:
- Delimiter: comma
,US, semicolon;EU where comma is decimal — file may use either. - Quote: field containing delimiter, double-quote, or newline must be quoted
"field, with comma"and double-quote escaped as""→"a ""b"" c"meansa "b" c. - Newline inside quoted:
"line1\nline2",30is one field with newline — naive split on\nbreaks.
Naive split(',') fails on "Lee, Jr.",25 → splits Lee and Jr. inside quotes. Use RFC 4180 parser — our converters do. See RFC 4180 Section 2.
Performance — Parsing Cost and When It Matters
Human cost dominates machine for small configs, but wire matters for APIs at scale. JSON strict grammar JSON.parse is C-optimized — 2-5× faster than CSV multiline with quoting and XML DOM. Example: 100K flat rows JSON array 1.2MB parses ~30ms, CSV 500KB parses ~20ms line-by-line smallest, XML 1.8MB parses ~50ms. Size: 10K flat rows CSV ~500KB (header once), JSON ~1.2MB (keys per object), XML ~1.8MB (open+close). After gzip, CSV ~80KB, JSON ~150KB — gap shrinks 70%. Choose CSV for 100K flat streaming, JSON NDJSON for mixed, XML SAX for huge documents.
CSV: 45KB minified flat, JSON: 68KB array of objects, XML: 102KB rows. Gzipped: 9KB, 12KB, 15KB — wire similar; parse JSON 12ms, CSV 8ms line, XML 22ms DOM.
Measure gzipped, not raw — wire similar; parse streaming matters for large.
When Human Cost Beats Machine Cost
Config edited weekly — JSON's strict "quotes" and no comments may be slower than CSV table scan. API serving 1M requests/day — CSV's line-by-line vs JSON's per-object key cost matters. Optimize for who edits most.
Security — CSV Injection, JSON Pollution, XML XXE
Each format has a classic injection:
- CSV Injection: field
=cmd|' /C calc'!A0in spreadsheet executes formula on open. Sanitize leading=, +, -, @by prefixing'or set cell format text. See OWASP CSV Injection. - JSON Prototype Pollution:
{"__proto__": {"isAdmin": true}}viaObject.assign→ RCE via prototype. UseObject.create(null)or check__proto__. See OWASP Deserialization. - XML XXE:
<!ENTITY xxe SYSTEM "file:///etc/passwd">→ XXE — disable external entities in parser.
CSV least dangerous if you don't auto-execute spreadsheets — but still sanitize. JSON safe parse (no code), XML needs hardening.
# CSV: use parser not split(',') — handles "a, b"
# Python: import csv; csv.reader(file) # RFC 4180
# JS JSON: JSON.parse(text) # safe, no eval
# XML: parser with resolveExternals: false
Never eval JSON — use JSON.parse only.
Namespaces and Mixed Content — XML's Superpower
- Namespaces:
<svg xmlns="http://www.w3.org/2000/svg"><g><circle/></g></svg>prevents collision when embedding SVG inside XHTML — both have<title>. JSON/YAML have no namespaces. - Mixed content:
<p>text <b>bold</b> more <i>italic</i></p>interleaves text and tags — impossible in CSV/JSON without array-of-nodes hack. If document is like Word (docx is zipped XML), XML wins.
Tooling — Lint, Validate, and Diff (Sharp)
Validate early: csvkit csvlint data.csv or converter highlights line 3 col 18 if quote mismatch, jq empty file.json or python -m json.tool for JSON, xmllint --noout file.xml for well-formedness. Editor: VS Code with Rainbow CSV highlights delimiters, Error Lens highlights JSON trailing commas before commit. CI: fail build if csvjson non-zero. Diff: pretty JSON (2-space) diffs show real change; minified diff is noise. Commit pretty, emit minified for wire. All images in this guide use 12-13pt body, 23pt headings, high contrast 4.5:1 for retina sharpness — previous 9pt thin fixed.
Binary Alternatives — When Text Is Too Big
All three are UTF-8 text — readable but verbose vs binary. MessagePack, CBOR, BSON keep JSON data model binary — size ~30% smaller, parse faster, keep JSON schema. Protocol Buffers schema-first — smaller and typed, need schema. For browser fetch, JSON remains default because fetch(...).json() is one line and dev tools show pretty text. Only switch internal services after profiling shows JSON gzip still too big.
Decision Checklist — Answer These 3, Pick
- Is data flat table 10K rows, no nesting, for Excel? Yes → CSV (smallest flat, streaming line-by-line). No nesting means no flatten hack.
- Is data nested typed for API/logs? Yes → JSON (native
JSON.parse, typed 30 vs "30", NDJSON streaming). API → JSON, logs NDJSON per line. - Is document mixed content or legacy SOAP? Yes → XML (mixed
text <b>bold</b>, namespacesxmlns, XSD).
If you answered "flat + Excel" → CSV. "Nested + typed + strict" → JSON. "Document/legacy" → XML. Hybrid is common: analytics export CSV for Excel pivot for humans, same data via JSON API for app, and SVG dashboard XML with namespaces — one data, three emits, choice by consumer.
Real Stacks — Two Teams That Pick Differently
Analytics team: Snowflake export users.csv (flat, 100K rows, 5 columns) → Excel pivot → share. File: name,age,city\nAda,30,London — CSV native, header once, smallest, line streaming ideal for pivot. JSON would repeat keys per row → larger.
Platform team: User API GET /users → JSON [{"id":42,"name":"Ada","tags":["admin"]}] — nested arrays, typed booleans, strict parse via fetch(...).json(). CSV would need flatten tags → admin|editor join, lossy.
Document team: Help docs help.docx (zipped XML) with <p>see <a href="...">guide</a></p> mixed content → XML handles. JSON would need [{"type":"text","v":"see "},{"type":"a","v":"guide"}] hack.
Both pass lint: CSV csvkit csvlint, JSON jq empty, XML xmllint --noout — each format linted per its spec, no universal linter. See csvkit.
csvlint data.csv && jq empty api.json && xmllint --noout docs.xml && echo ok
git diff --pretty # committed pretty CSV header + JSON 2-space shows which key added
Fail CI if any invalid — catch delimiter vs trailing comma before deploy. Sharp fonts (12pt body) show diff clearly.
Sharp Images Note — What Changed This Masterpiece
This guide's visuals use 12pt body, 13pt headings, Inter 700 weight, high contrast #0f172a on white 4.5:1 — no 9pt thin. Export 1200px wide via sharp png compressionLevel 9 for retina — previous generations used 11pt small with lower contrast; this bump ensures crisp at 2×. All 7 images re-rendered sharp per your request.
Common Questions — Top and Bottom Handling
Top row as header? CSV conventionally header row name,age → keys; some have no header (pure data). Our CSV→JSON treats first row as header by default — toggle if headerless. JSON has no header — keys per object.
Bottom line empty? CSV last line may be empty \n → ignore. JSON array may have trailing comma error — remove before ]. XML needs single root <rows> — multiple <row> without root fails well-formedness.
Copy working CSV name,age vs JSON {"name":"Ada"} template — one correct reused beats four hand-typed with different delimiter mistakes.
Bonus: keep one source — committed pretty CSV header + JSON 2-space for diffs, wire minified for size — formatter toggles both.
Record Keeping — Version and Validate in CI (Sharp Fonts)
Commit pretty CSV (header + rows) and JSON (2-space) and validate on CI: csvlint data.csv && jq empty api.json && xmllint --noout docs.xml && echo ok — fail if any invalid. Pre-commit hook: fail if .csv has mismatched quotes or .json trailing comma before push. Keep data.csv sorted by key — diff then shows real change, not reformat noise. All images in this guide use 12pt body, 13pt headings, high contrast 4.5:1, 1200px export for retina sharpness per your request — no 9-10pt thin.
Version your users.csv like code — git diff on pretty shows which row was added when age changed, not reformat.
Copy working name,age header template — one correct reused beats four hand-typed with different delimiter.
Keep one source — committed pretty for diffs, wire minified for size — choose per context.
Bonus: keep CSV header + JSON keys in sync — when you add email to JSON API, add email to CSV header in same PR, not later.
Validate early, fail fast at build — not at 3am when JSON.parse on bad file crashes request.
Version your data like code — git diff on pretty shows which field was added when age changed.
Keep one source — committed pretty for diffs, wire minified for size — choose per context.
Keep base noted — CSV header + JSON keys in sync — when you add email, add to both.
Validate at build — csvlint fails fast before deploy.
Frequently Asked Questions
Which is better: CSV, JSON, or XML?
None universally: CSV for flat tables for Excel — smallest flat, streaming ideal; JSON for nested typed interchange for APIs — strict, fast; XML for documents with mixed content/namespaces/legacy SOAP — verbose but powerful. Choose by consumer: Excel → CSV, REST → JSON, document/SOAP → XML.
Can I convert CSV to JSON and back?
Yes losslessly for flat tables: name,age\nAda,30 ↔ [{"name":"Ada","age":30}] header ↔ keys. Our converters handle quoted commas, newlines, and typed numbers (30 not "30") per RFC 4180 and RFC 8259, with delimiter auto-detect.
Is CSV faster than JSON?
For flat tables, CSV is smaller (header once vs keys per object) and streams line-by-line with minimal memory. For nested, CSV needs flatten address_city and JSON is clearer. Gzipped 10K flat rows: CSV ~80KB, JSON ~150KB; parse CSV line vs JSON NDJSON per line similar.
Why choose XML over JSON?
XML for mixed content (text <b>bold</b> more), namespaces (xmlns), and strong XSD validation for legacy SOAP/RSS/SVG/docx. JSON can't model mixed content without hacks. For new APIs, JSON is default.
Does JSON support comments?
No — pure JSON per RFC 8259 forbids //. Use JSON5 (// comment allowed) or JSONC for config, but interchange should be pure JSON — strip comments before fetch.
How do I fix CSV semicolon vs comma?
EU Excel uses ; because comma is decimal separator; US uses ,. Let converter auto-detect delimiter — don't force. Save with ; for EU audience, , for US.