Skip to main content
Reference · 8 min read

The YAML Norway Problem, and When It Applies

Why `country: no` becomes false in some YAML parsers and stays a string in others, shown with real output from PyYAML and js-yaml side by side.

The story is a classic. You have a list of ISO country codes in a YAML config. Norway's code is NO. Your parser reads it as the boolean false, Norway vanishes from the list, and something downstream breaks in a way that takes an afternoon to trace.

The story is also, increasingly, out of date — and the advice people repeat alongside it is not quite right. Whether no becomes false depends on which version of the YAML spec your parser implements, and the two common answers disagree completely.

The same file, two parsers

Here is the test document:

country: no
enabled: yes
off_switch: off
version: 1.0
zip: 01234
when: 2024-01-01
time_like: 12:30:00

Loaded with PyYAML 6.0.3 via yaml.safe_load, which implements the YAML 1.1 resolver:

country      -> False                          (bool)
enabled      -> True                           (bool)
off_switch   -> False                          (bool)
version      -> 1.0                            (float)
zip          -> 668                            (int)
when         -> datetime.date(2024, 1, 1)      (date)
time_like    -> 45000                          (int)

Loaded with js-yaml 4, which defaults to the YAML 1.2 core schema:

country      -> "no"                           (string)
enabled      -> "yes"                          (string)
off_switch   -> "off"                          (string)
version      -> 1                              (number)
zip          -> 1234                           (number)
when         -> "2024-01-01T00:00:00.000Z"     (string)
time_like    -> "12:30:00"                     (string)

Same bytes in, four materially different values out. These are real outputs, not reconstructions.

Two surprises bigger than Norway

Everyone quotes the boolean case. The other two rows in the PyYAML output deserve more attention than they get.

`zip: 01234` becomes 668

Not 1234. 668. YAML 1.1 treats a leading zero as an octal prefix, and 01234 in octal is 668 in decimal. A postal code does not merely lose its leading zero — it becomes an entirely unrelated number, with nothing to signal that it happened.

`time_like: 12:30:00` becomes 45000

YAML 1.1 has sexagesimal (base-60) integers, so 12:30:00 is read as 12×3600 + 30×60 = 45,000 seconds. A duration, a timestamp column, a cron-ish field — all silently integers.

YAML 1.2 removed both of these. If you have only ever used a 1.2 parser, you may never have met them; if your config passes through a 1.1 parser anywhere in its life, they are waiting.

Which parser do you have?

Roughly, and worth verifying rather than assuming:

ParserDefault behaviourNorway problem?
PyYAML (safe_load)YAML 1.1 resolverYes
ruamel.yaml, default round-tripYAML 1.2No
js-yaml v4YAML 1.2 core schemaNo
js-yaml v3YAML 1.1 style typesYes
Go gopkg.in/yaml.v2YAML 1.1Yes
Go gopkg.in/yaml.v3YAML 1.2 leaningNo
SnakeYAML (Java)YAML 1.1Yes

The quickest way to find out is to run the seven-line document above through whatever actually loads your config, and look at the types. It takes a minute and settles the question for your stack specifically, which is worth more than any table.

The fix is two characters

Quote anything whose type you care about. That is the whole answer, it works under every parser and every spec version, and it costs nothing:

country: "no"
enabled: "yes"
version: "1.0"
zip: "01234"
when: "2024-01-01"
time_like: "12:30:00"

A useful habit: quote every value that is an identifier rather than a quantity. Country codes, postal codes, version strings, phone numbers, SKUs, account numbers, git refs. If you would never do arithmetic on it, it is a string, and saying so removes an entire category of bug.

Converting to JSON makes it visible

JSON has no implicit typing: a value is quoted or it is not, and nothing is inferred from how it is spelled. That makes a round trip a good way to see what your parser actually decided.

Paste a config into the YAML to JSON converter and read the output. Anything that came out as true, false or a bare number when you meant a string is a bug you just caught for free. The converter follows the YAML 1.2 core schema, so it will also show you what a modern parser sees — which may differ from what your production stack sees, and that difference is itself worth knowing.

Going the other way, the JSON to YAML converter quotes ambiguous scalars automatically. A string "no" comes out as 'no' and a string "1.0" as '1.0', so the result survives a round trip through a 1.1 parser unchanged.