Anchors (&name) and aliases (*name) reuse a node. Merge keys (<<) copy a mapping’s entries into another, as docker-compose and many CI files do. YAML 1.2 has no merge keys, so oneyaml reads << as a plain key unless you pass MergeKeys().
package main import ( "fmt" "log" oneyaml "oneyaml.dev/go" ) const src = `defaults: &defaults image: nginx:1.27 replicas: 2 web: <<: *defaults replicas: 3 worker: *defaults ` type Svc struct { Image string `yaml:"image"` Replicas int `yaml:"replicas"` } func main() { // Aliases are expanded on read; `<<` is a plain key in YAML 1.2. var plain map[string]any if err := oneyaml.Unmarshal([]byte(src), &plain); err != nil { log.Fatal(err) } fmt.Println("worker:", plain["worker"]) fmt.Println("web without merge keys:", plain["web"]) // MergeKeys reads docker-compose-style files the way YAML 1.1 tools do. var merged map[string]Svc if err := oneyaml.Unmarshal([]byte(src), &merged, oneyaml.MergeKeys()); err != nil { log.Fatal(err) } fmt.Printf("web: %+v\n", merged["web"]) // A Document keeps anchors and aliases as written, and can follow them. doc, err := oneyaml.Parse([]byte(src)) if err != nil { log.Fatal(err) } worker, _ := doc.Root().Lookup("worker") target := worker.Target() image, _ := target.Lookup("image") fmt.Printf("worker is *%s; &%s has image %s\n", worker.AliasName(), target.Anchor(), image.Value()) }
worker: map[image:nginx:1.27 replicas:2] web without merge keys: map[<<:map[image:nginx:1.27 replicas:2] replicas:3] web: {Image:nginx:1.27 Replicas:3} worker is *defaults; &defaults has image nginx:1.27
Good to know
- Keys in the mapping win over merged ones:
web.replicasis 3, not 2. - Alias expansion is bounded by default, so a “billion laughs” file fails with a
*LimitErrorinstead of exhausting memory (read untrusted YAML safely). - A
Documentkeeps&and*as written.AliasName()names the anchor an alias refers to,Target()follows it, andAnchor()is a node’s own anchor. - An edit below an alias is refused with
ErrAliasWrite, because it would change every place that shares the node. Replacing the alias node itself is allowed. WithMergeKeys, setting a key that only a<<source has adds a key of the mapping’s own.
Merge keys across Go, Rust and C# libraries, measured: YAML anchors, aliases and merge keys explained.