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 set MergeKeys = true.
using OneYaml; const string Src = """ defaults: &defaults image: nginx:1.27 replicas: 2 web: <<: *defaults replicas: 3 worker: *defaults """; // Aliases are expanded on read; `<<` is a plain key in YAML 1.2. var plain = YamlSerializer.Deserialize<Dictionary<string, Dictionary<string, object?>>>(Src)!; Console.WriteLine($"worker.image = {plain["worker"]["image"]}"); Console.WriteLine($"web has image without merge keys: {plain["web"].ContainsKey("image")}"); // MergeKeys reads docker-compose-style files the way YAML 1.1 tools do. var merged = YamlSerializer.Deserialize<Dictionary<string, Svc>>(Src, new YamlSerializerOptions { MergeKeys = true })!; Console.WriteLine($"web = {merged["web"]}"); // A document keeps anchors and aliases as written, and can follow them. using var doc = YamlDocument.Parse(Src); YamlNode worker = doc.Root["worker"]; Console.WriteLine($"worker is {worker.Kind}, target &{worker.Target.Anchor}, image {doc.Get<string>("/worker/image")}"); // An edit through an alias would change every place that shares it, so it is refused. try { doc.Set("/worker/replicas", 5); } catch (YamlException e) when (e.Category == YamlErrorCategory.RefusedAlias) { Console.WriteLine(e.Message); } record Svc(string Image, int Replicas);
worker.image = nginx:1.27 web has image without merge keys: False web = Svc { Image = nginx:1.27, Replicas = 3 } worker is Alias, target &defaults, image nginx:1.27 The edit would write through an alias and change every place that shares the anchored node.
Good to know
- Keys in the mapping win over merged ones:
webhas 3 replicas, not 2. - Alias expansion is bounded by default, so a “billion laughs” file fails with a
Limiterror instead of exhausting memory (read untrusted YAML safely). - A
YamlDocumentkeeps&and*as written. An alias node hasKindAlias,Targetfollows it, andAnchoris a node’s own anchor. Reads through an alias, such asGet("/worker/image"), follow it. - An edit below an alias is refused with
RefusedAlias, because it would change every place that shares the node. Replacing the alias node itself is allowed.
Merge keys across Go, Rust and C# libraries, measured: YAML anchors, aliases and merge keys explained.