Skip to content

Edit a value

Change an existing scalar

err := doc.Set("server.port", 9090)

The node is mutated in place, so its inline comment and quoting style survive:

port: 8080   # the port we bind to
# becomes
port: 9090   # the port we bind to

A value that was quoted stays quoted; one that was bare stays bare.

doc.Set("name", "after")   // quoted: "before"  →  quoted: "after"
doc.Set("kind", "after")   // kind: before      →  kind: after

Set accepts anything yaml.Marshal accepts: strings, booleans, all integer and float widths, nil, and maps, slices or yaml-tagged structs for whole subtrees.

Vet strings that came from a user

Keeping a bare scalar bare has a sharp edge. A string that YAML would need quoted is written unquoted over a plain scalar, so Set("greeting", "hello # world") emits greeting: hello # world and the file now says hello with a comment after it. Empty strings, leading spaces and number-like text go the same way, and the emit-time guard does not catch any of them because the output is valid YAML.

Values you author are fine. For anything else, read Assigning a string that needs quoting and vet the value first.

Create a key

Setting a path that does not exist creates it, appended to the deepest existing ancestor. Appending rather than inserting is deliberate: existing keys keep their comments and their order.

err := doc.Set("server.timeout", "30s")
server:
  host: localhost   # where we bind
  port: 8080
  timeout: 30s      # ← added here

Missing intermediate mappings are synthesised:

err := doc.Set("server.tls.cert", "/etc/cert.pem")
server:
  host: localhost
  tls:
    cert: /etc/cert.pem

Replace a subtree

Passing a map replaces the addressed node wholesale — keys absent from the map are gone.

err := doc.Set("avatars.matt", map[string]any{
    "likeness": "a bearded developer",
})

You own what you replace

A subtree replaced from a Go value cannot keep what the Go value does not carry. Comments inside that subtree, anchors, and block scalar styles are lost, because nothing in the map describes them.

For a targeted change, address the leaf directly (avatars.matt.likeness) rather than replacing its parent. Reach for subtree replacement when you genuinely own the whole value.

Change one item in a list

You cannot, directly. A path addresses mapping keys and has no index syntax, so the smallest thing you can change inside a sequence is the whole sequence:

n, _ := doc.Get("servers")          // the sequence, as a collection node
doc.Set("servers", []string{"alpha", "beta"})   // replaces all of it

Anything written inside that list — comments, anchors, block scalars — goes with the replacement, because the Go value you supply does not carry it.

Beware the shape that looks like it should work:

doc.Set("servers[0].port", 9090)   // no error, and no help

That creates a top-level key literally named servers[0]. See Path syntax.

What cannot be created

A path that runs through a scalar is refused rather than silently rewriting it:

// given "a: 1"
err := doc.Set("a.b.c", "value")   // ErrNotFound: "a" is not a mapping

Creating it would mean destroying a, which you did not ask for.

Nor can a key be created in a document that has no root mapping — an empty file, or one whose root is a sequence or a scalar. yamldoc edits documents; it does not author them.

Errors worth branching on

switch {
case errors.Is(err, yamldoc.ErrNotFound):     // path does not resolve
case errors.Is(err, yamldoc.ErrInvalidPath):  // malformed path expression
case errors.Is(err, yamldoc.ErrUnsupported):  // document cannot be edited safely
}

Each message and its cause is in Errors.