Recursive AI

Write YAML tokens in C#

OneYaml is a free, open-source YAML library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

Write YAML key-value pairs and lists directly into a UTF-8 buffer.

Terminal
dotnet add package OneYaml

The package targets .NET 8 and .NET 10; the download runs on .NET 10.

C#
using OneYaml;
using System.Buffers;
using System.Text;
 
var buffer = new ArrayBufferWriter<byte>();
var emitter = new Utf8YamlEmitter(buffer);
emitter.WriteStartMapping();
emitter.WritePropertyName("ports");
emitter.WriteStartSequence();
emitter.WriteNumberValue(80);
emitter.WriteEndSequence();
emitter.WriteEndMapping();
Console.Write(Encoding.UTF8.GetString(
    buffer.WrittenSpan));
Text
ports:
  - 80

Unzip the example, then dotnet run.

The full program

The full program writes two ports and deserializes the emitted text into a typed record. No input files are needed. Unbalanced or out-of-order emitter calls throw InvalidOperationException.

C#
using OneYaml;
using System.Buffers;
using System.Text;
 
var buffer = new ArrayBufferWriter<byte>();
var emitter = new Utf8YamlEmitter(buffer);
emitter.WriteStartMapping();
emitter.WritePropertyName("ports");
emitter.WriteStartSequence();
emitter.WriteNumberValue(80);
emitter.WriteNumberValue(443);
emitter.WriteEndSequence();
emitter.WriteEndMapping();
string yaml = Encoding.UTF8.GetString(buffer.WrittenSpan);
Console.Write(yaml);
var service = YamlSerializer.Deserialize<Service>(yaml)!;
Console.WriteLine($"Parsed ports: {string.Join(", ", service.Ports)}");
 
record Service(int[] Ports);

dotnet run prints:

Text
ports:
  - 80
  - 443
Parsed ports: 80, 443

Variations

Strings that look like another scalar type are quoted automatically:

C#
using OneYaml;
using System.Buffers;
using System.Text;
 
var buffer = new ArrayBufferWriter<byte>();
var emitter = new Utf8YamlEmitter(buffer);
emitter.WriteStringValue("true");
Console.Write(Encoding.UTF8.GetString(buffer.WrittenSpan));
Text
'true'

The short answer

Create a Utf8YamlEmitter with an IBufferWriter<byte>, then write balanced mapping and sequence start/end calls. Use WritePropertyName for a mapping key and a Write…Value method for its value. Decode the written bytes with UTF-8 when you need a .NET string. The program above then parses that string, so it checks both the emitted structure and the values it contains.

This is useful when you already have a stream of values or want precise control over which nodes you write. If you already have a class representing the output, YamlSerializer.Serialize avoids spelling out the structure token by token.

How do I generate a YAML file?

The emitter produces bytes, not a file path. After the full program has filled buffer, save its contents:

C#
File.WriteAllBytes("out.yaml", buffer.WrittenSpan.ToArray());

This writes out.yaml with the same content the program prints:

YAML
ports:
  - 80
  - 443

No YAML source file is required: the literal port numbers in the program are its input.

For each mapping, write a property name followed by exactly one value. That value can itself be a sequence or mapping. In the program, ports owns a sequence, and each number belongs to that sequence. Close the sequence before closing the outer mapping. You do not need to add document markers, indentation or newlines yourself.

Ending a collection that is not open, writing a value where a key is required, or writing a second tag on the same node throws InvalidOperationException. Treat that as an error in the calling code, rather than as malformed user YAML. The emitter guide documents this order checking.

How do I write YAML tags in C#?

Call WriteTag before writing the node it applies to. The tag argument is the resolved tag: the core string tag is tag:yaml.org,2002:str, not the shorthand !!str. Anchors and aliases also have explicit emitter methods. These are node properties, not mapping keys; do not put them into WritePropertyName and expect tag semantics.

For a tagged scalar, use the variation’s buffer and emitter setup with a fresh buffer, then replace its write and print calls with:

C#
emitter.WriteTag("!ref");
emitter.WriteStringValue("defaults");
Console.Write(Encoding.UTF8.GetString(buffer.WrittenSpan));
Text
!ref defaults

Use the value method matching your intended type. The variation writes the string true, so the emitter quotes it to keep it a string on reading. Writing a boolean value instead would express a boolean. Letting the emitter decide quoting keeps strings from silently changing type.

All oneyaml C# recipes