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 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:
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);
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:
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);
# 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:
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; } }
web x2, public: True name: web replicas: 2 public: true
Swap the package and the two using lines:
dotnet remove package YamlDotNet dotnet add package OneYaml.Compat.YamlDotNet --version 1.2.1
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; } }
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 withEnsureRoundtrip(). - 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.Savewrites without comments, as YamlDotNet’s does. UseYamlDocumentfor 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.0 | VYaml 1.5.0 | OneYaml 1.2.1 | |
|---|---|---|---|
| API | System.Text.Json style (README) | [YamlObject] partial types (README) | System.Text.Json style, plus YamlDocument |
| YAML version | 1.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, 755 | not measured | refused, 755 |
| Native AOT | Source generator in the package | Source generator | Source 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 YamlDotNet | New API | New API | Compatibility package, then new API |
| Edit a file and keep comments | Not documented as such; README lists a syntax tree API | Not documented | YamlDocument |
| JSON Patch and Merge Patch on YAML | Not documented | Not documented | RFC 6902 and RFC 7396 (how) |
| License | MIT | MIT | MIT 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.