Recursive AI

Replacing YamlDotNet

Looking for a YamlDotNet alternative? What YamlDotNet does to a file you edit and save, the options, and a one-package swap that keeps your builder code.

Developers look for a YamlDotNet alternative for three reasons: loading a YAML file into its YamlStream, changing a value and saving it drops every comment and blank line, its typed deserializer reads YAML 1.1 (yes is true, 0755 is 493), and native AOT needs a separate generator package. The main alternatives are SharpYaml 3 (System.Text.Json-style, YAML 1.2), VYaml (fast, Unity-first) and OneYaml, which edits files without touching their comments and has a compatibility package, OneYaml.Compat.YamlDotNet, that keeps your DeserializerBuilder code compiling after a package swap.

What YamlDotNet does to a file you edit and save

Here is a typical hand-maintained file:

values.yaml
# values.yaml for the web service
image:
  repository: registry.example.com/web
  tag: "1.4.2"   # set by CI
 
replicas: 2      # scaled by the release job
 
# Hosts served by the ingress
hosts:
  - example.com
  - www.example.com

The usual YamlDotNet way to change one value is to load it into a YamlStream, as in its wiki sample, change the node, and save:

Program.csYamlDotNet 18.1.0 · .NET 10
using YamlDotNet.RepresentationModel;
 
var stream = new YamlStream();
using (var reader = new StreamReader("values.yaml"))
{
    stream.Load(reader);
}
 
var root = (YamlMappingNode)stream.Documents[0].RootNode;
root.Children[new YamlScalarNode("replicas")] = new YamlScalarNode("3");
 
stream.Save(Console.Out, assignAnchors: false);
Output
image:
  repository: registry.example.com/web
  tag: "1.4.2"
replicas: 3
hosts:
- example.com
- www.example.com
...

With YamlDotNet 18.1.0, all four comments and both blank lines are gone, the list is no longer indented under hosts, and a ... document-end marker has been added. The quotes around "1.4.2" survive. Write that back to a file in a repository and the diff covers the whole file.

The same edit with OneYaml’s YamlDocument, and two more:

Program.csOneYaml 1.2.1 · .NET 10
using OneYaml;
 
using var doc = YamlDocument.Parse(File.ReadAllText("values.yaml"));
 
doc.Set("/image/tag", "1.5.0");         // stays double-quoted
doc.Set("/replicas", 3);                // its comment stays
doc.Add("/hosts/-", "api.example.com"); // block style, like its siblings
 
File.WriteAllText("values.yaml", doc.ToString());
Console.Write(doc);
Output
# values.yaml for the web service
image:
  repository: registry.example.com/web
  tag: "1.5.0"   # set by CI
 
replicas: 3      # scaled by the release job
 
# Hosts served by the ingress
hosts:
  - example.com
  - www.example.com
  - api.example.com

Only the three values change; every comment, blank line and quote stays. Each value keeps its style, and the new host is laid out like its siblings. More on what an edit keeps in edit YAML without losing comments and the C# recipe.

Swap YamlDotNet for OneYaml with one package

Rewriting every call site isn’t needed to start. OneYaml.Compat.YamlDotNet provides YamlDotNet’s serializer builders and representation model under new namespaces, tested against YamlDotNet 18.1.0. Take a YamlDotNet program that reads a file with a YAML 1.1 yes in it:

Program.csYamlDotNet 18.1.0 · .NET 10
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
 
IDeserializer deserializer = new DeserializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .IgnoreUnmatchedProperties()
    .Build();
var service = deserializer.Deserialize<Service>("name: web\nreplicas: 2\npublic: yes\nlegacy: true\n");
Console.WriteLine($"{service.Name} x{service.Replicas}, public: {service.Public}");
 
ISerializer serializer = new SerializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .Build();
Console.Write(serializer.Serialize(service));
 
class Service
{
    public string Name { get; set; } = "";
    public int Replicas { get; set; }
    public bool Public { get; set; }
}
Output
web x2, public: True
name: web
replicas: 2
public: true

Swap the package and the two using lines:

TerminalNuGet
dotnet remove package YamlDotNet
dotnet add package OneYaml.Compat.YamlDotNet --version 1.2.1
Program.csOneYaml.Compat.YamlDotNet 1.2.1 · .NET 10
using OneYaml.Compat.YamlDotNet.Serialization;                   // was: YamlDotNet.Serialization
using OneYaml.Compat.YamlDotNet.Serialization.NamingConventions; // was: YamlDotNet.Serialization.NamingConventions
 
IDeserializer deserializer = new DeserializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .IgnoreUnmatchedProperties()
    .Build();
var service = deserializer.Deserialize<Service>("name: web\nreplicas: 2\npublic: yes\nlegacy: true\n");
Console.WriteLine($"{service.Name} x{service.Replicas}, public: {service.Public}");
 
ISerializer serializer = new SerializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .Build();
Console.Write(serializer.Serialize(service));
 
class Service
{
    public string Name { get; set; } = "";
    public int Replicas { get; set; }
    public bool Public { get; set; }
}
Output
web x2, public: True
name: web
replicas: 2
public: true

The output is identical: the adapter reads YAML 1.1 forms such as yes the way YamlDotNet does, so existing files keep their meaning. The package page lists what it covers and every difference (OneYaml.Compat.YamlDotNet).

What the adapter doesn’t cover

  • Doesn’t compile: the scanner, MergingParser, type inspectors, node deserializers, event emitters, object-graph visitors, static contexts and comment events.
  • Throws NotSupportedException: Deserialize(IParser) with a parser that isn’t the adapter’s own, and a serializer built with EnsureRoundtrip().
  • Behaves differently: serializer output can quote more, more valid YAML is read, exception classes can differ, and depth and alias limits always apply.
  • Still drops comments: after you change a node, the adapter’s YamlStream.Save writes without comments, as YamlDotNet’s does. Use YamlDocument for edits.

The order to check them in is in migrating from YamlDotNet, with the OneYaml API for each YamlDotNet call. Move file by file: the adapter and the OneYaml package work side by side.

YamlDotNet alternatives compared

SharpYaml 3.16.0VYaml 1.5.0OneYaml 1.2.1
APISystem.Text.Json style (README)[YamlObject] partial types (README)System.Text.Json style, plus YamlDocument
YAML version1.2 Core by default, other schemas selectable“YAML 1.2 mostly supported”1.2 Core
yes into a bool, 0755 into an int (measured)refused, 755not measuredrefused, 755
Native AOTSource generator in the packageSource generatorSource generator in the package
Targets (NuGet).NET Standard 2.0, .NET 8, .NET 10.NET Standard 2.0 and 2.1, .NET 6, 8, 9; Unity.NET 8, .NET 10
Migration from YamlDotNetNew APINew APICompatibility package, then new API
Edit a file and keep commentsNot documented as such; README lists a syntax tree APINot documentedYamlDocument
JSON Patch and Merge Patch on YAMLNot documentedNot documentedRFC 6902 and RFC 7396 (how)
LicenseMITMITMIT OR Apache-2.0

The measured row comes from the program in choosing a C# YAML library, run on 26 October 2026.

When to stay on YamlDotNet

  • You target .NET Framework or .NET Standard: OneYaml runs on .NET 8 and .NET 10 only.
  • You depend on type inspectors, node deserializers or event emitters, which neither the adapter nor OneYaml provides.
  • You read and write YAML 1.1 files as YAML 1.1 and nothing edits them by hand.
  • Your code works and nothing above is a problem for you. YamlDotNet is maintained: 18.1.0 shipped in June 2026.

For the full picture of .NET YAML libraries, see choosing a C# YAML library; to start with OneYaml directly, the getting started guide and the swap recipe.