When should I use JSON and when YAML?
JSON vs YAML: When to Use Which
Use JSON when machines are the main reader and writer: API payloads, messages between services, data stored or logged by programs, anything that crosses a language boundary. Use YAML when humans edit the file by hand and need comments: deployment manifests, CI pipelines, application configuration. JSON's strictness is a feature on the wire because every parser agrees on what a document means. YAML's flexibility is a feature in an editor, but it comes with implicit typing rules that differ between YAML versions and libraries, and those differences are where most conversion bugs come from.
The short comparison
| JSON | YAML | |
|---|---|---|
| Specification | RFC 8259 / ECMA-404, a few pages | YAML 1.2.2, a long and intricate spec; many libraries still implement 1.1 |
| Comments | None | # to end of line |
| Structure | Braces and brackets | Indentation (with optional JSON-style flow collections) |
| Strings | Always double-quoted | Plain, single-quoted, double-quoted, literal block, folded block |
| Types | Six, always explicit from syntax | Resolved from the text of plain scalars by a schema |
| References | None | Anchors &, aliases *, merge keys << in most libraries |
| Several documents per file | No (NDJSON is a separate convention) | Yes, separated by --- |
| Parser agreement | Very high | Varies by version and library |
| Typical use | APIs, storage, logs, lockfiles | Kubernetes, CI pipelines, Compose files, app config |
YAML 1.2 was designed so that every JSON document with unique keys is also a valid YAML document. That makes converting JSON to YAML safe in principle. The other direction, YAML to JSON, is where you need to pay attention.
When JSON is the right choice
- Data exchange. A JSON document means the same thing to
JSON.parse, Python'sjson, Go'sencoding/jsonand Java's Jackson. With YAML, the same file can load as different data depending on the library, as the next section shows. - Generated files. Lockfiles, build manifests and anything a program rewrites. Programs do not need comments, and JSON round-trips through a parser without losing anything a program cares about.
- Security-sensitive input. JSON parsers only build plain data. Some YAML loaders can construct arbitrary language objects from tags (PyYAML's
yaml.loadwithoutSafeLoader, older SnakeYAML defaults); always use the safe loading function when YAML comes from outside. - Performance. JSON parsers are simpler and faster. For large payloads, the difference is measurable.
When YAML is the right choice
- Files people edit and review. Comments explain why a value is set. Block syntax produces clean diffs, one value per line.
- Long text. Literal blocks (
|) hold shell scripts, SQL and certificates without escaping every newline and quote. - Repetition. Anchors and aliases let a base configuration be declared once and reused, which JSON cannot express.
- Ecosystem convention. If the tool you are configuring expects YAML (Kubernetes, most CI systems, OpenAPI documents written by hand), use it. Fighting the convention costs more than any format preference saves.
A reasonable rule: if the file has a human author, YAML; if it has a program author, JSON. TOML is a third option for flat-to-moderately-nested configuration that wants comments without significant indentation.
Conversion gotchas
The Norway problem
YAML 1.1 resolved a long list of plain words as booleans: yes, no, on, off, y, n, true, false, in several capitalisations. A list of country codes containing NO for Norway therefore loaded as false. YAML 1.2 fixed this: its core schema recognises only true and false (in lowercase, title case or uppercase). But the most widely installed libraries, including PyYAML and Ruby's Psych, still implement 1.1 rules.
Here is the same file loaded three ways. The first column is what the YAML to JSON converter produces, since it follows YAML 1.2:
countries: [GB, NO, SE]
enabled: yes
mode: 0755
version: 1.10
time: 12:30
released: 2026-09-26{
"countries": [
"GB",
"NO",
"SE"
],
"enabled": "yes",
"mode": 755,
"version": 1.1,
"time": "12:30",
"released": "2026-09-26"
}| Plain scalar | YAML 1.2 core schema | PyYAML safe_load (1.1) |
Ruby Psych safe_load (1.1) |
|---|---|---|---|
NO |
"NO" |
False |
false |
yes |
"yes" |
True |
true |
y |
"y" |
'y' |
"y" |
0755 |
755 |
493 (octal) |
493 (octal) |
1.10 |
1.1 |
1.1 |
1.1 |
12:30 |
"12:30" |
750 (base 60) |
45000 |
2026-09-26 |
"2026-09-26" |
datetime.date |
Date |
on as a key |
"on" |
True |
true |
Every row is a real bug somebody has shipped: a version number 1.10 that became 1.1, a file mode that changed base, a time that became an integer. Two YAML 1.1 libraries do not even agree with each other on 12:30. The last row matters for CI files that use on: as a top-level key: a 1.1 loader gives you a boolean key.
The defence is simple. Quote any scalar that is meant to be a string but looks like something else: country and language codes, version numbers, octal-looking values, times, dates you want as text, and yes/no/on/off. version: "1.10" means the same thing to every parser.
JSON to YAML: know your reader
When you convert JSON to YAML, the converter decides which strings need quotes. The Formattr converter targets YAML 1.2, so it quotes "1.10" (which would otherwise be read as a number) but leaves NO, yes and 12:30 plain, because a 1.2 parser reads them as strings:
countries:
- GB
- NO
- SE
enabled: yes
version: "1.10"
time: 12:30That is correct YAML 1.2. If the file will be read by a 1.1 library, add quotes to those values before you ship it.
Anchors, aliases and merge keys
defaults: &defaults
adapter: postgres
host: localhost
development:
<<: *defaults
database: dev_db&defaults names a node, *defaults refers to it, and << merges the referenced mapping into the current one, with local keys winning. Three things to know:
- JSON has no references. Converting to JSON expands every alias into a full copy. The output is correct but larger, and editing one copy no longer changes the others.
- Merge keys are not part of YAML 1.2.
<<was a YAML 1.1 type. Most libraries still support it because so many real files use it, but support is a library option, not a guarantee. A converter that does not implement it emits a literal key named"<<"containing the base mapping, instead of merged keys. The YAML to JSON converter resolves them, sodevelopmentabove becomes{"adapter": "postgres", "host": "localhost", "database": "dev_db"}; other tools in your pipeline may not, so check their output too. - Aliases can be abused. A small document with nested aliases can expand exponentially (the "billion laughs" pattern). Loaders cap alias expansion; if you hit the cap, the file is either malicious or badly structured.
Comments are lost
JSON has nowhere to put them, so YAML to JSON drops every comment. Going back from JSON to YAML cannot recover them. If a YAML file is the source of truth, convert it to JSON as a build step and never edit the JSON.
Multiple documents
A YAML file can hold several documents separated by ---, which is how Kubernetes manifests bundle a Deployment and a Service. JSON has no equivalent. The YAML to JSON converter wraps them in an array by default (the Multiple documents as array option); with the option off, only the first document is converted and a notice says how many were skipped.
Numbers and special values
YAML 1.2 supports .inf, -.inf and .nan. JSON does not, and JSON.stringify writes them as null, so in many tools a YAML value of .inf becomes null after conversion without an error. The Formattr converter also writes null, but says so in a notice: "JSON has no Infinity or NaN — 1 value was written as null." Large integers have the same problem as in JSON itself: anything beyond 2^53 − 1 loses precision in tools that read numbers as JavaScript doubles. The Formattr converters keep every digit in both directions, but the next program in the chain may not, so keep large identifiers quoted in both formats.
Keys that are not strings
YAML allows any node as a mapping key, including numbers, booleans and even sequences. JSON keys are always strings. 200: OK in an OpenAPI responses block becomes "200": "OK"; a 1.1 loader turns on: into the boolean true before the converter ever sees it.
A conversion checklist
- Validate the source first. Use the YAML Formatter or the JSON Validator; a conversion of broken input only moves the error.
- Quote ambiguous scalars in YAML before converting: codes, versions, times, octal-looking strings, yes/no words.
- Check anything involving
<<merge keys in the output of every tool that reads the file, not only the converter. - Expect comments and anchors to disappear in YAML to JSON, and decide which file is the source of truth.
- For multi-document YAML, decide whether you want an array or separate files.
- If the YAML fails to parse at all, the problem is almost always indentation; the YAML indentation guide explains how the rules actually work.