---
title: "Anchors, aliases and merge keys"
url: https://recursiveai.org/oneyaml/csharp/cookbook/anchors-aliases-merge-keys/
description: "Read YAML anchors and aliases in C#, turn on << merge keys for docker-compose-style files with MergeKeys, and follow an alias to its anchor in a YamlDocument."
version: "1.2.1"
---

# Anchors, aliases and merge keys

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`.

```csharp
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](https://recursiveai.org/oneyaml/csharp/cookbook/untrusted-input/)).
- **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](https://recursiveai.org/oneyaml/articles/yaml-anchors-aliases-merge-keys/).
