fileview.dev

YAML, from the syntax up

11 min read

Written while building a parser-backed YAML viewer, so every trap below is one that produced a real support question rather than a hypothetical.

YAML is an indentation-based format for data that humans write by hand. Every JSON document is already valid YAML; what YAML adds is comments, multi-line strings, references between values, and a type system that guesses — which is where almost every YAML bug comes from.

YAML is what you get when a data format is optimised for the person typing it rather than the machine reading it. That trade is mostly a good one — configuration is read by machines once and edited by people constantly — but it means the format makes decisions on your behalf, and knowing which decisions those are is the whole skill.

The shape of a document

Two structures carry everything: mappings (key and value) and sequences (ordered items). Nesting is expressed with indentation, and indentation is spaces only. A tab character is not merely discouraged, it is illegal, and the resulting parse error rarely mentions tabs.

name: checkout-service
replicas: 3
enabled: true
ports:
  - 8080
  - 8443
resources:
  limits:
    memory: 512Mi
    cpu: "500m"
  requests:
    memory: 256Mi
Mappings, sequences and nesting

Scalars, and the types you did not ask for

An unquoted scalar has its type inferred. That is convenient until the inference is wrong, and it is wrong in a small number of predictable places. Quoting is the fix in every one of them, which is why experienced YAML looks over-quoted to newcomers.

Values that do not mean what they look like
WrittenParsed asWrite instead
version: 1.20the number 1.2version: "1.20"
country: NOthe boolean falsecountry: "NO"
zip: 01234octal, or a string, depending on the parserzip: "01234"
id: 12:30a sexagesimal number in YAML 1.1id: "12:30"
value: nullnull, not the text “null”value: "null"
time: 22:00:00a timestamp, not a stringtime: "22:00:00"

The rule that survives every edge case: if a value is an identifier, a code, a version or anything else whose string form matters, quote it. The cost is two characters and the alternative is an outage that reproduces only in the environment where the value happened to look like a number.

Multi-line strings

This is the feature that makes YAML worth using for configuration, and the one most people copy without understanding. Two indicators, and a modifier that decides what happens to the final newline.

literal: |
  line one
  line two
    indented further

folded: >
  this becomes one long line
  because single newlines are
  folded into spaces

no_trailing_newline: |-
  exactly this, with nothing after it

keep_every_newline: |+
  trailing blank lines are preserved
Literal, folded, and the chomping modifiers

Anchors, aliases and merge keys

YAML can reference a value it has already defined. An anchor names a node, an alias reuses it, and the merge key folds a mapping into another. It is the closest thing the format has to a variable, and it is why a converted YAML file is often much larger than the original.

defaults: &defaults
  retries: 3
  timeout: 30
  log_level: info

staging:
  <<: *defaults
  host: staging.internal

production:
  <<: *defaults
  host: api.example.com
  log_level: warn
Define once, reuse

Merge keys are a YAML 1.1 extension rather than part of YAML 1.2, so support varies. Where they work, a later key wins over the merged one — production above keeps warn. Converting this to JSON expands every alias, because JSON has no way to express a reference; the data is identical and the file is bigger.

Several documents in one file

Three dashes start a new document in the same stream. Kubernetes manifests use this constantly, and it is the reason a file that looks like one object sometimes refuses to parse as one: a tool expecting a single document stops at the first separator, or fails outright.

---
kind: Service
name: checkout
---
kind: Deployment
name: checkout
replicas: 2
Two documents, one file

Comments

Anything from an unquoted hash to the end of the line is a comment. This is the single feature people miss most when they are forced back to JSON — and it is worth knowing that comments live only in the text, not in the parsed data, so any tool that reads and rewrites a YAML file discards them unless it was specifically built not to.

Reading a YAML error

Open a YAML file and see it parsedRuns in your browser; the file is never uploaded.

Questions

Is JSON valid YAML?

Yes, since YAML 1.2 — the specification defines JSON as a subset, so any valid JSON document is a valid YAML document. The reverse is not true: anchors, comments and multi-document files have no JSON equivalent.

Why can I not use tabs in YAML?

The specification forbids them for indentation, because a tab’s visual width depends on the editor while YAML’s structure depends on column position. Most parsers report it as an unexpected character rather than naming the tab, which is why it wastes a disproportionate amount of time.

What is the difference between the pipe and the greater-than sign?

The pipe preserves every newline in the block; the greater-than sign folds single newlines into spaces so wrapped prose arrives as one line. Blank lines are kept as breaks by both. Use the pipe for anything whitespace-sensitive, such as an embedded script.

Do comments survive when YAML is converted to JSON?

No, and they cannot: JSON has no comment syntax. Comments exist only in the YAML text and are discarded the moment it is parsed, so any round trip through another format loses them.