YAML vs JSON: Which Should You Use, and When?
YAML vs JSON: both hold the same data, but only one is pleasant to hand-edit. Learn the real differences, the classic YAML gotchas, and which to pick when.
Open almost any modern repository and you will find both formats sitting side by side: package.json and tsconfig.json in the root, docker-compose.yml next to them, and a .github/workflows/ci.yml one folder down. Same project, same developers, two different formats.
That is not inconsistency. It is a sensible division of labour, and once you see the logic behind it, choosing between YAML and JSON stops being a matter of taste.
They describe the same thing
Start with the most important fact: YAML and JSON model the same data. Both give you mappings (objects), sequences (arrays), strings, numbers, booleans, and null. Anything you can express in one, you can express in the other.
YAML 1.2 was deliberately defined as a superset of JSON, so a valid JSON document is also a valid YAML document. That is why converting between them is mechanical, and why a round trip through YAML ⇄ JSON preserves your data.
The same object in both:
{
"service": "api",
"replicas": 3,
"ports": [8080, 8443],
"env": { "LOG_LEVEL": "debug" }
}
service: api
replicas: 3
ports:
- 8080
- 8443
env:
LOG_LEVEL: debug
Identical data. The difference is entirely in the ergonomics.
JSON: strict on purpose
JSON’s rules are almost aggressively minimal, and that is its strength:
- Every key is a double-quoted string — no single quotes, no bare words.
- No trailing commas.
- No comments. At all.
- No date type, and no integer/float distinction in the spec — just “number”.
The payoff is that JSON parsers are small, fast, and boringly predictable. Two implementations will agree on what a document means. That is exactly what you want when a document is produced by one machine and consumed by another a thousand times a second.
The cost is that JSON is unpleasant to write by hand: deeply nested braces, mandatory quotes on every key, and a syntax error every time you append a line to a list and forget the comma above it.
YAML: optimized for humans
YAML trades strictness for readability. It drops most punctuation, uses indentation for structure, and — critically — supports comments:
# Bumped for the Black Friday load test. Revert in December.
replicas: 12
database:
host: db.internal
port: 5432
# pool size tuned against the read replica
pool: 20
That comment is the single biggest reason config files are YAML. A configuration value with no record of why it was set is a landmine for the next person. JSON gives you nowhere to write that explanation, which is why JSON-based config formats keep sprouting non-standard comment extensions.
Indentation and the tab rule
YAML uses spaces for indentation and forbids tab characters in indentation. This trips people up constantly, and the reason is worth knowing: a tab has no fixed width. If tabs were allowed, the same file would nest differently depending on your editor’s tab setting, and the document’s meaning would depend on how you happened to look at it. Banning tabs makes indentation unambiguous.
Set your editor to insert spaces in .yml and .yaml files and this entire class of error disappears. When a parser complains about a character that “cannot start any token”, a stray tab is the usual culprit.
Anchors and aliases
YAML can define a block once and reuse it, which JSON cannot do at all:
defaults: &defaults
adapter: postgres
pool: 5
development:
<<: *defaults
database: app_dev
test:
<<: *defaults
database: app_test
&defaults creates an anchor, *defaults references it, and << merges the referenced map into the current one. Useful for repetitive config — and worth using sparingly, because a reader now has to jump around the file to know what any given block actually contains.
The YAML gotchas everyone eventually hits
YAML infers the type of unquoted values, and its guesses are occasionally wrong in ways that reach production.
The Norway problem. In YAML 1.1 — still what several widely used parsers implement — yes, no, on, off, y, and n are booleans. So a list of country codes does something horrible:
countries:
- GB
- NO # parsed as false, not the string "NO"
- FR
Version numbers become floats. version: 1.10 parses as the number 1.1, while 1.2.3 stays a string because it is not a valid number. Your version field silently changes type depending on how many dots it has.
Leading zeros. code: 012 may be read as an octal number rather than the string 012 — a genuine problem for zip codes and account numbers.
Colons in values. Times like 12:30 were parsed as base-60 numbers by 1.1-era parsers, which is why MAC-address-like values misbehave.
The fix for every one of these is the same: quote the string. "NO", "1.10", "012". If a value is meant to be text, say so explicitly instead of relying on inference.
One more thing worth knowing: YAML’s spec is far larger than JSON’s, so YAML parsers are correspondingly larger, and some can instantiate arbitrary language objects from a document. Load untrusted YAML with a safe loader — Python’s yaml.safe_load rather than yaml.load, for example.
So which do you pick?
Choose JSON when a machine is on both ends. API request and response bodies, log lines, queue payloads, browser storage — anything serialized at volume. It parses fast, it is universally supported, and its rigidity is a feature when nobody is reading it. If you are shipping JSON over the wire, strip the whitespace with a Code Minifier and pretty-print it only when you need to read it.
Choose YAML when a human edits it and commits it. CI pipelines, Kubernetes manifests, Ansible playbooks, application config, static-site front matter. Comments, readability, and a low punctuation tax matter far more here than parse speed.
A useful tiebreaker: if the file lives in version control and gets reviewed in pull requests, YAML’s clean diffs are worth a lot. If the file is generated and consumed programmatically, use JSON and never look at it.
Converting between them
Because the data model is shared, conversion is straightforward — with one caveat. Going YAML → JSON drops comments and expands anchors, because JSON can represent neither. That direction is lossy for the humans, not for the data.
JSON → YAML is safe, and is a good way to make a machine-generated config readable before you start editing it.
Paste either format into YAML ⇄ JSON to convert and validate in one step; it runs in your browser, so config files containing internal hostnames never get uploaded anywhere. To tidy or validate JSON on its own, use the JSON Formatter.
Quick answers
Is JSON valid YAML? Under YAML 1.2, yes — the spec makes JSON a subset, and most modern parsers accept a JSON document happily.
Why can’t I use tabs in YAML? Tab width varies by editor, so tab-based indentation would make nesting ambiguous. Spaces only.
Can I put comments in JSON? Not in standard JSON. Some tools accept JSONC or JSON5 variants, but a plain parser will reject a // line.
Which is faster? JSON, comfortably — smaller grammar, simpler parsers. It rarely matters for a config file read once at startup; it matters a great deal on a hot API path.
Why did my YAML string turn into a boolean? You almost certainly hit the yes/no/on/off rule. Wrap the value in quotes.
The takeaway
YAML and JSON are not rivals so much as the same data wearing different clothes. JSON is a wire format that humans occasionally read; YAML is a human format that machines occasionally read. Pick based on who is doing the typing, quote your ambiguous strings, and convert freely when a file needs to cross the line.