JSON vs YAML vs CSV: convert without losing data
· 7 min read
Open the JSON / YAML / CSV tool
Converting between JSON, YAML and CSV looks like a solved problem. There are hundreds of one-click converters. Then the country code NO comes back as false, ZIP code 01234 loses its leading zero in Excel, and a nested address object turns into the text [object Object].
Each format has a different data model, and conversion is where those differences bite. This article compares the three formats and walks through the specific things that go wrong, with examples you can reproduce in the JSON ↔ YAML ↔ CSV converter.
Three formats, three data models
| Format | Data model | Types | Typical use |
|---|---|---|---|
| JSON | Tree of objects, arrays and scalars | string, number, boolean, null | APIs, config written by programs |
| YAML | A superset of JSON’s tree, plus anchors and tags | Inferred from how a value is written | Config written by people (CI, Kubernetes, Compose) |
| CSV | A flat table of strings | None: every cell is text | Spreadsheets, exports, data exchange |
JSON and YAML describe the same kind of tree, so converting between them is mostly lossless (comments aside). CSV is a different shape entirely: a table has no nesting and no types, so something has to decide how a tree becomes rows and how text becomes numbers again.
JSON and YAML
YAML 1.2 is a superset of JSON: any JSON document is valid YAML. That makes JSON → YAML straightforward and YAML → JSON mostly so. The problems come from YAML’s type guessing.
The Norway problem
In YAML, an unquoted value’s type is inferred from how it looks. YAML 1.1 (2005) treated many words as booleans:
countries:
- GB
- NO # YAML 1.1: false
- SE
NO, no, off, yes, on, y and n were all booleans in YAML 1.1. YAML 1.2 (2009) fixed this by keeping only true and false, but many parsers still implement 1.1 rules. That includes PyYAML, the default in a lot of Python tooling, and Go’s gopkg.in/yaml.v2.
The same family of problems:
| Written as | YAML 1.1 reads it as | YAML 1.2 (core schema) reads it as |
|---|---|---|
no |
false |
"no" |
010 |
8 (octal) |
10 |
1:20 |
80 (sexagesimal) |
"1:20" |
2026-09-30 |
a date object | "2026-09-30" |
1_000 |
1000 |
"1_000" |
The safe fix when writing YAML is to quote any string that some parser might read as something else. The converter does that automatically: it reads YAML with 1.2 rules, and when it writes YAML it quotes every string that a 1.1 or 1.2 parser would misread, so "no" stays "no" whichever parser loads the file.
Anchors, merge keys and multiple documents
YAML can do several things JSON can’t. Anchors (&base) and aliases (*base) reuse a block, and merge keys (<<: *base) copy its fields:
defaults: &defaults
retries: 3
timeout: 30
production:
<<: *defaults
timeout: 60
Converted to JSON, the aliases are expanded, so production becomes {"retries": 3, "timeout": 60}. That’s correct but not reversible: converting back gives you the expanded version, not the anchors.
Alias expansion is also an attack vector. A few lines of nested aliases can expand to gigabytes (the “billion laughs” attack), so a converter should cap the expansion. The one here stops after 100 aliases.
A YAML file can contain several documents separated by ---, which is common in Kubernetes manifests. There’s no JSON equivalent, so the converter returns an array of documents and tells you it did.
What doesn’t survive
- Comments: JSON has none, so every
#comment is lost on the way to JSON. - Custom tags: CloudFormation’s
!Refand!GetAttare YAML tags with no JSON meaning. The value is kept and the tag is dropped, with a warning. .infand.nan: valid YAML floats, but JSON has no way to write them.
JSON and CSV
This is where most data loss happens.
Flattening nested data
CSV is a table, so nested objects have to be flattened. The usual convention is dot-separated column names:
[{ "id": 1, "address": { "city": "Oslo" }, "tags": ["a", "b"] }]
id,address.city,tags.0,tags.1
1,Oslo,a,b
Reading the CSV back with “unflatten” rebuilds the nesting: keys 0, 1 and so on become arrays again. The other convention puts JSON text in a cell ("{""city"":""Oslo""}"). That keeps everything in one column but makes the file harder to use in a spreadsheet.
Two edge cases to know about. A key that already contains a dot ("a.b": 1) becomes ambiguous after flattening. And if a and a.b both exist, one of them has to lose. The converter warns about both instead of dropping data silently.
Types: everything in CSV is text
A CSV cell has no type. 123 could be a number or a string, true could be a boolean or a word, and an empty cell could be null or "". Converters have to guess, and guessing loses information unless the writer and reader agree on a convention.
The convention the converter uses:
- A number is written bare (
123), and a string that only looks like a number is quoted ("123"). On the way back, only unquoted cells get type detection. nullis an empty cell, and an empty string is"".- Leading zeros (
007,01234) aren’t valid JSON numbers, so they are always read as strings. - Big numbers such as
9007199254740993are kept exactly, not rounded.
With these rules, JSON → CSV → JSON returns the original data for flat objects. Missing keys are the one exception: a CSV row has a cell for every column, so a missing key comes back as null. You can check a round trip yourself by pasting the original and the result into JSON Diff.
Excel is its own format
Most CSV files end up in Excel, which has its own ideas:
- Encoding: Excel on Windows reads a CSV as the local ANSI code page unless the file starts with a UTF-8 byte order mark (BOM), so Cyrillic or accented text turns into mojibake. The converter’s download adds the BOM by default.
- Delimiter: in locales that use a comma as the decimal separator (Germany, Russia, much of Europe), Excel expects
;between fields. When reading, the converter detects,,;, tab and|automatically; when writing, pick the delimiter your users need. - Type guessing: Excel turns
01234into1234, long numbers into1.23457E+15and some gene names into dates. Quoting doesn’t stop it. The reliable fix is to import the column as text (Data → From Text/CSV) rather than double-clicking the file.
CSV injection
A cell that starts with =, +, - or @ can be run as a formula when the file is opened in a spreadsheet. If a CSV export contains user-supplied text (names, comments), someone can put =HYPERLINK(...) in their profile and it will run on an admin’s machine. OWASP calls this CSV injection. The usual defence is to prefix such cells with ', which the converter does when “Escape formulas” is on (headers included). It’s off by default because it changes the data.
Which format when?
- JSON for anything a program writes and another program reads. It’s strict, and its types survive the trip.
- YAML for configuration people edit by hand, where comments and readability matter. Quote strings liberally, and know which YAML version your parser speaks.
- CSV for tabular data headed to a spreadsheet or a database import. Agree on the delimiter, encoding and null convention with whoever reads it.
When you have to convert, check what came out. If the source is JSON, validate and tidy it in the JSON formatter first. Then paste the result into the converter, convert it back, and compare the two versions. If they differ, you’ve found your data loss before your users do.