jsontreeappOpen a file

Multi-document YAML

You try to read a Kubernetes manifest and the parser says expected a single document in the stream, but found more. Nothing is wrong with the file. You called the wrong function.

What the three dashes mean

A line containing only --- starts a new document. One file, several independent values, read in order:

apiVersion: v1
kind: Service
metadata:
  name: api
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api

That is two documents, not one document with two halves. There is no shared root between them, and anchors defined in the first are not visible in the second. A leading --- before the first document is optional and common. A trailing ... ends a document explicitly and is rare in the wild.

This is why every Kubernetes example you have ever copied is a multi-document file. It is the natural way to put a Service and a Deployment in one kubectl apply.

The mistake

Most YAML libraries give you two functions. One reads a single document and throws if it finds a second. The other reads them all. The single one usually has the shorter name, so it is the one everybody reaches for.

// js-yaml
yaml.load(text)      // throws on the file above
yaml.loadAll(text)   // returns an array of documents

# PyYAML
yaml.safe_load(text)      # throws
yaml.safe_load_all(text)  # generator over documents

# Go, gopkg.in/yaml.v3
yaml.Unmarshal(data, &v)             // first document only
yaml.NewDecoder(r).Decode(&v)        // call in a loop until io.EOF

The error message is honest but it reads like the file is broken, so people go and look at the file. It is worth recognising the sentence on sight, because it always means the same thing.

Handling both without thinking about it

If you do not know in advance which kind of file you have, read them all and unwrap when there is only one. That way a single document still behaves like itself instead of turning into a one element array:

const docs = yaml.loadAll(text);
const value = docs.length === 1 ? docs[0] : docs;

This is what jsontree.app does. A normal YAML file opens as itself, and a manifest opens as a list you can expand document by document.

Two other things that trip people up

Three dashes inside a block scalar

A --- only separates documents when it is at the start of a line and outside any block. Inside a literal block it is just text, and a correct parser knows that. If your own splitting code uses text.split("---"), it does not, and it will quietly cut a document in half.

Anchors do not cross the boundary

defaults: &def
  retries: 5
---
service:
  <<: *def          # error: unknown anchor 'def'

Anchors are scoped to a document. If you want shared values across documents you have to repeat them, or generate the file from something else.

Try it

Drop a manifest on jsontree.app. Several documents come through as a list, one document as itself, and a syntax error tells you the line it happened on.

Open a file