Install OneYaml for .NET, read YAML into C# types, write it back, edit a file in place without losing its comments, and apply JSON Patch and Merge Patch.
OneYaml is lossless, comment-preserving editing and a System.Text.Json-style serializer for YAML 1.2, in one package. Both run on one parser, so a typed read and an edit never disagree about what a file means. Every sample on this page was compiled and run against OneYaml 1.2.1 from NuGet on .NET 10; the output under each one is what it printed.
Install
OneYaml supports .NET 8 and .NET 10.
dotnet new console -o YamlStart cd YamlStart dotnet add package OneYaml --version 1.2.1
The namespace is OneYaml. The package has no dependencies outside .NET and no unsafe code, and a source generator in the package covers trimmed and native AOT apps. The license is MIT or Apache-2.0, at your choice.
The samples are complete Program.cs files with top-level statements. A few read a file from the current directory; the YAML they read is shown with them.
Read YAML into a type
YamlSerializer.Deserialize<T> binds YAML to classes and records the way JsonSerializer binds JSON. Keys bind in camel case by default, so replicas fills Replicas.
using OneYaml; var cfg = YamlSerializer.Deserialize<Config>(""" name: api replicas: 2 ports: [80, 443] debug: false """)!; string ports = string.Join(", ", cfg.Ports); Console.WriteLine($"{cfg.Name} runs {cfg.Replicas} replicas on {ports} (debug: {cfg.Debug})"); record Config(string Name, int Replicas, List<int> Ports, bool Debug);
api runs 2 replicas on 80, 443 (debug: False)YAML 1.2 is the default: yes is a string and 0755 is the integer 755. A value that doesn’t fit its type is a YamlException with a line, a column and a path (see errors with line and column).
Write YAML
YamlSerializer.Serialize writes any value as block-style YAML.
using OneYaml; var cfg = new Config("api", 3, [80, 443], new() { ["tier"] = "web" }); Console.Write(YamlSerializer.Serialize(cfg)); record Config( string Name, int Replicas, List<int> Ports, Dictionary<string, string> Labels);
name: api replicas: 3 ports: - 80 - 443 labels: tier: web
YamlSerializerOptions sets naming policies, ignore conditions, converters and more, as JsonSerializerOptions does (Serializer).
Edit a file and keep its comments
To change a file that people also edit by hand, parse it into a YamlDocument instead. An edit changes only what you touch, in the old value’s style; comments and layout elsewhere stay byte for byte.
using OneYaml; using var doc = YamlDocument.Parse(""" # Deploy settings name: api replicas: 2 # raised by the release job ports: [80] """); // Read a value by JSON Pointer. Console.WriteLine($"replicas = {doc.Get<int>("/replicas")}"); // Change a value, append to a list, add a key. doc.Set("/replicas", 3); doc.Add("/ports/-", 443); doc.Root["region"] = "us-east-1"; Console.Write(doc);
replicas = 2 # Deploy settings name: api replicas: 3 # raised by the release job ports: [80, 443] region: us-east-1
Paths are JSON Pointers (RFC 6901): /replicas, /spec/containers/0/image, and /ports/- for the end of a list. doc.Root reads like a JsonNode: node["key"], node[0], and assignment to change a value. A new value copies the style of its neighbors, so the flow list [80] stays a flow list (Editing).
Apply a JSON Patch or a Merge Patch
A YamlDocument applies RFC 6902 JSON Patch and RFC 7396 Merge Patch to the YAML text, atomically: if any operation fails, the document is unchanged.
using OneYaml; const string Src = """ # Web service image: web:1.0 # pinned env: DEBUG: "1" LOG: info ports: [80] """; // JSON Patch (RFC 6902): all or nothing. using (var doc = YamlDocument.Parse(Src)) { doc.ApplyPatch(""" [ { "op": "replace", "path": "/image", "value": "web:1.1" }, { "op": "add", "path": "/ports/-", "value": 443 } ] """); Console.Write(doc); } Console.WriteLine("---"); // Merge Patch (RFC 7396): null removes a key. using (var doc = YamlDocument.Parse(Src)) { doc.ApplyMergePatch("""{ "env": { "DEBUG": null, "LOG": "warn" } }"""); Console.Write(doc); }
# Web service image: web:1.1 # pinned env: DEBUG: "1" LOG: info ports: [80, 443] --- # Web service image: web:1.0 # pinned env: LOG: warn ports: [80]
Patches use the YAML data model: test compares YAML values, so 1 and 1.0 differ. ApplyPatch also takes a JsonNode or a YamlPatch built in code (Patches).
Where next
Recipes, each a complete program:
- Parse YAML into C# classes and read YAML without a class
- Edit YAML and keep its comments and merge or patch YAML
- Convert YAML to JSON and back and read Kubernetes manifests and multi-document files
- Report errors with line and column, anchors, aliases and merge keys
- Read untrusted YAML safely, use YAML in a native AOT app and switch from YamlDotNet with one package
The docs:
- Serializer, editing, patches, limits and the reader and emitter. The API reference is the XML documentation your IDE shows.
- Moving from another library: YamlDotNet and SharpYaml, with the compatibility packages OneYaml.Compat.YamlDotNet and OneYaml.Compat.SharpYaml.
- Choosing a library: how it compares, performance and conformance.
- What changed and what stays stable: the changelog and stability.
Choosing a C# YAML library: YamlDotNet, SharpYaml, VYaml and OneYaml compared.