YAML to JSON Converter

YAML to JSON and back, with anchors, block scalars and the Norway problem

YAML's whole promise is that a configuration file should be readable without quotes and brackets. Everything difficult about it follows from that: with no quotes, the reader has to guess what a value is.

The Norway problem

country: NO

YAML 1.1   ->  { "country": false }
YAML 1.2   ->  { "country": "NO" }

Norway's country code is NO, and in YAML 1.1 that is the
boolean false. So is n, no, off and, in some readers, N.
Most libraries still default to 1.1.

This is the most famous YAML bug and it is not a bug, it is the specification. YAML 1.1, from 2005, defines a long list of words that mean true and false. YAML 1.2, from 2009, cut it to exactly true andfalse. Most libraries still implement 1.1, or some hybrid of the two, because changing it would break files that already exist.

Switch the schema above and watch the same document change meaning. The practical rule is short: quote anything that could be read as something else. "NO", "on", "1.20","yes". It costs two characters.

version: 1.20     ->  1.2      the trailing zero is gone
version: "1.20"   ->  "1.20"   because it is a string

id: 0755          ->  493      octal in YAML 1.1
port: 8080        ->  8080
time: 12:30       ->  750      sexagesimal in YAML 1.1, still
                             supported by some readers

Two ways to write text over several lines

literal: |
  one
  two

folded: >
  one
  two

  three

literal  ->  "one\ntwo\n"       breaks kept
folded   ->  "one two\nthree\n" single breaks become spaces,
                                blank lines stay breaks

| is a literal block: every line break is kept, and everything indented past the block's own indentation keeps that indentation too. It is what you want for a script, a certificate, a SQL query or a log excerpt.

> is folded: single line breaks become spaces, so a paragraph can be wrapped in the source and arrive as one long line. Blank lines survive as real breaks, and a more-indented line keeps its breaks, so a folded block can still hold a code sample.

The indicator nobody remembers

|    clip    one newline at the end. The default
|-   strip   no newline at the end
|+   keep    every trailing newline, however many there were

The default keeps exactly one newline at the end. That single character is behind an entire genre of bug: a shell script in a YAML block that works, and a password or a key in one that does not, because the trailing newline went along with it. If you are putting a secret in a block scalar, you almost certainly want |-.

Anchors, aliases and the merge key

defaults: &defaults
  adapter: postgres
  pool: 5

development:
  <<: *defaults
  database: helio_dev

  ->  { adapter: postgres, pool: 5, database: helio_dev }

&name marks a node, *name refers back to it, and <<: merges a mapping's entries into the current one with local keys winning. It is the closest YAML has to a variable, and it is what makes a Docker Compose or CI file with six near-identical jobs bearable.

Two things to know. An alias is a reference to the same node, not a copy. In a language with mutable structures, changing one changes the other. And JSON has no way to express any of it, so an alias becomes a second copy of the value on the way out, and a file that was three lines of YAML can become a large amount of JSON.

The merge key is a YAML 1.1 feature that never made it into 1.2 as such. It is supported here because everything supports it in practice.

Indentation, and the two characters that are never allowed

Structure is indentation, so a tab is not whitespace in YAML. It is a syntax error. Editors that insert tabs are the single most common cause of a file that looks correct and will not parse. Tabs at the start of a line are converted to two spaces each here instead of being refused, so at least you can see what the file was meant to say.

The other one is a colon followed by a space inside an unquoted value.time: 12: 30 is two keys and an error;time: "12: 30" is a string. A colon with no space after it is fine, so URLs work without quoting.

What does not survive the trip to JSON

YAMLBecomes
CommentsNothing. JSON has none, and they are gone for good
Anchors and aliasesRepeated values, one copy per reference
.inf, .nannull, with a note. JSON has no way to write either
DatesStrings. JSON has no date type, and YAML 1.1 readers turn them into date objects
Complex keysRefused. A JSON key is always a string
Multiple documentsSeveral JSON values, one after another
Tags such as !!python/objectIgnored, with a note. This is also why yaml.load without a safe loader is a remote code execution vulnerability

Going the other way

JSON is valid YAML already. The flow forms [a, b] and{a: 1} are simply how YAML writes JSON. So the interesting part of converting is working out what does not need quoting.

The rule used here is to quote when leaving it unquoted would change what the value is. A string of "true" or "42" or"no" gets quotes; hello world does not. Strings with line breaks become literal blocks, because that is the point of them.