YAML vs JSON for configuration: comments, anchors, and the Norway problem
YAML reads better than JSON and that is exactly why it is dangerous: the same features that make it pleasant to write also let it guess the type of your values, and its guesses are sometimes wrong in ways that pass every test until production.
What YAML gives you that JSON does not
JSON is deliberately minimal: objects, arrays, strings, numbers, booleans, null, and nothing else. No comments, no variables, no references. That minimalism is a feature for data interchange and a limitation for human edited configuration.
YAML adds the things config authors want:
- Comments with
#, which JSON has no syntax for at all. For a file humans maintain, this alone is a strong argument. - Anchors and aliases for reuse, so a block defined once can be referenced elsewhere.
- Less punctuation, using indentation instead of braces, which makes deep structures easier to scan.
defaults: &defaults
timeout: 30
retries: 3
production:
<<: *defaults
timeout: 60
Here &defaults names a block, *defaults references it, and <<: merges it in. Production inherits the retries and overrides the timeout. There is no way to express this reuse in JSON; you would repeat the values.
The ambiguity cost
The price of YAML's friendliness is that unquoted scalars are interpreted, not taken literally. YAML tries to infer whether 3.0 is a number or a string, whether yes is a boolean, and whether 12:30 is a time. Usually it is right. When it is wrong, nothing errors; you just get the wrong type.
The famous case is the Norway problem. A list of country codes:
countries:
- GB
- NO
- SE
In older YAML 1.1 parsers, NO is parsed as the boolean false, because no, off, and n are all boolean literals. Your list of strings silently contains a false. The same family of bugs bites version numbers (1.20 becomes the number 1.2, losing the trailing zero), MAC addresses and git SHAs that look like numbers, and leading zero values like 08 that some parsers reject as invalid octal.
| Value you wrote | What YAML may infer | Intended |
|---|---|---|
NO | boolean false | string "NO" |
yes | boolean true | string "yes" |
1.20 | number 1.2 | string "1.20" |
08 | invalid octal or error | string "08" |
3:30 | 210, sexagesimal | string "3:30" |
The fix is to quote any scalar whose type you care about: "NO", "1.20". But the burden is on you to remember, and the failure is silent, which is the worst kind.
Other YAML footguns
Significant whitespace means a stray space or a tab versus spaces can change structure or fail to parse, and the error messages are often unhelpful. Anchors, while powerful, can be abused to build deeply nested expansions that make files hard to reason about, and the merge key << has inconsistent support across parsers.
When JSON is the safer choice
Reach for JSON when the file is machine generated or machine consumed more than hand edited, when type correctness matters and you cannot afford silent coercion, or when you want a format with one unambiguous interpretation across every parser. JSON's lack of comments is a real cost, but its lack of surprises is a real benefit.
A reasonable middle ground many teams adopt: author config in YAML for readability, but validate it against a schema so a false where a string belongs is caught at load time rather than at runtime. The schema turns YAML's silent coercion into a loud failure, which is exactly what you want.
Convert between the two formats and inspect how your YAML scalars resolve into JSON types with the YAML to JSON converter.