Skip to content

Path syntax

Every addressing method — Get, Keys, Set, Remove — takes the same kind of path. It is smaller than it looks, and knowing exactly how small saves an afternoon.

The grammar

path    = segment ( "." segment )*
segment = one literal mapping key

That is the whole of it. A path is a dotted list of mapping keys, walked from the document root. There is no wildcard, no filter, no escape character and no quoting.

doc.Get("server.tls.cert")   // root → "server" → "tls" → "cert"
doc.Keys("")                 // the top-level keys

An empty path is only meaningful to Keys, where it means the document root. Get, Set and Remove reject it.

What a path cannot address

This is the section most people arrive here for. None of the following work, and most of them fail quietly rather than loudly.

Sequence elements

There is no index syntax. A YAML sequence is opaque to a path: you can address the key that holds it and replace the whole thing, but you cannot reach an item inside it.

servers:
  - name: alpha
    port: 8080
  - name: beta
doc.Get("servers")            // ok — the whole sequence, as a collection node
doc.Set("servers", []string{"alpha", "beta"})  // ok — replaces it wholesale
doc.Get("servers.name")       // not found; a sequence is not a mapping

servers[0].port is not a path with an index in it — it is a path whose first segment is the literal key servers[0]. Nothing in the library rejects that, so:

doc.Set("servers[0].port", 9090)

creates a new top-level key literally named servers[0] containing a mapping with port: 9090, and reports no error. If you are building paths from user input, reject bracket syntax yourself before passing it on.

Editing inside a sequence means reading the sequence out, changing it in Go, and setting it back — which loses the comments and styles inside it, because the Go value does not carry them.

Keys containing a dot

A dot is always a separator, never part of a key. A document with

log.level: debug

lists log.level from Keys("") and cannot be addressed by any path: Get("log.level") looks for a key log holding a key level, finds no log, and returns false. Setting it creates the nested pair instead, leaving the flat key in place.

Anything below an alias

An *alias is a reference. The walk does not follow it, so Get("use.x") on use: *base is not found. Address the anchor definition instead.

Documents

A path starts inside one document. Which document you are in is decided by which *Document you called the method on; see File.Documents.

Where a walk stops, and what you get back

Path against a: 1 and b: {c: 2} Result
b.c resolves
b.d does not exist yet — Get false, Set creates it
b.d.e does not exist yet — Set creates d as a mapping holding e
a.b blocked: a is a scalar. Set returns ErrNotFound naming a, rather than destroying it
"" ErrInvalidPath: empty
a..b ErrInvalidPath: "a..b" has an empty segment
a. ErrInvalidPath — a trailing dot is an empty segment

The blocked case is deliberate. Creating a.b would mean deleting the scalar a first, which is not what the caller asked for, so the walk refuses instead of choosing for you.

Merge keys are addressable, which is rarely what you want

A mapping written with <<: *base has a real key named <<, so Keys lists it and Get("child.<<") resolves to the alias node.

Removing an anchor a merge key still uses is refused, naming the merge site:

removing "base" would leave alias *b at "child.<<" dangling

Case and whitespace

Keys match exactly — byte for byte, case-sensitively, with no trimming. A path is only rejected as malformed when a segment is empty; Set(" a", 1) addresses a key whose name begins with a space, and creates one if it is missing.