Recursive AI

Anchors, aliases and merge keys

All oneyaml C# recipes

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.

C#
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);
Text
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: web has 3 replicas, not 2.
  • Alias expansion is bounded by default, so a “billion laughs” file fails with a Limit error instead of exhausting memory (read untrusted YAML safely).
  • A YamlDocument keeps & and * as written. An alias node has Kind Alias, Target follows it, and Anchor is a node’s own anchor. Reads through an alias, such as Get("/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.