Skip to content
GitHubDiscord

YAML Serialization in Cortex

A lightweight, high-performance YAML serialization library for .NET that supports essential YAML features while maintaining simplicity and ease of use.

Add the Cortex.Serialization.Yaml package to your project:

dotnet add package Cortex.Serialization.Yaml

Or via Package Manager:

Install-Package Cortex.Serialization.Yaml
using Cortex.Serialization.Yaml;

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public bool IsActive { get; set; }
}

var person = new Person 
{ 
    Name = "John Doe", 
    Age = 30, 
    IsActive = true 
};

// Serialize to YAML
string yaml = YamlSerializer.Serialize(person);

// Output:
// name: John Doe
// age: 30
// isActive: true
var yaml = @"
name: Jane Smith
age: 25
isActive: true";

var person = YamlDeserializer.Deserialize<Person>(yaml);
Console.WriteLine(person.Name); // "Jane Smith"

The simplest way to serialize objects:

// With default settings
string yaml = YamlSerializer.Serialize(obj);

// With custom settings
var settings = new YamlSerializerSettings { EmitNulls = false };
string yaml = YamlSerializer.Serialize(obj, settings);

For multiple serializations with the same settings:

var serializer = new YamlSerializer(new YamlSerializerSettings 
{ 
    SortProperties = true,
    Indentation = 4
});

string yaml1 = serializer.Serialize(obj1);
string yaml2 = serializer.Serialize(obj2);

The serializer automatically handles:

TypeYAML Representation
stringScalar (quoted if needed)
booltrue / false
int, long, double, decimalNumeric scalar
DateTime, DateOnly, TimeOnlyISO 8601 string
GuidString representation
List<T>, T[]Sequence (- item)
Dictionary<K,V>Mapping (key: value)
Custom objectsMapping of properties
// Generic method
var person = YamlDeserializer.Deserialize<Person>(yaml);

// Non-generic method (runtime type)
var obj = YamlDeserializer.Deserialize(yaml, typeof(Person));
using var reader = new StreamReader("config.yaml");
var config = YamlDeserializer.Deserialize<Config>(reader);
var deserializer = new YamlDeserializer(new YamlDeserializerSettings
{
    CaseInsensitive = true,
    IgnoreUnmatchedProperties = true
});

var obj = deserializer.Deserialize<MyClass>(yaml);
PropertyTypeDefaultDescription
NamingConventionINamingConventionCamelCaseConventionProperty name transformation
EmitNullsbooltrueInclude null properties in output
EmitDefaultsbooltrueInclude default values in output
SortPropertiesboolfalseSort properties alphabetically
Indentationint2Spaces per indentation level
PreferFlowStyleboolfalseUse [...] and {...} for collections
FlowStyleThresholdint80Max line length for flow style
EmitCommentsbooltrueEmit associated comments
PropertyTypeDefaultDescription
NamingConventionINamingConventionCamelCaseConventionProperty name transformation
CaseInsensitivebooltrueIgnore case when matching properties
IgnoreUnmatchedPropertiesbooltrueSilently ignore unknown properties
PreserveCommentsboolfalsePreserve comments for round-trip
ResolveAnchorsbooltrueAutomatically resolve YAML aliases

Flow style provides compact, JSON-like syntax for simple collections:

Sequences:

# Block style (default)
tags:
  - web
  - api
  - production

# Flow style
tags: [web, api, production]

Mappings:

# Block style (default)
metadata:
  version: 1.0
  author: John

# Flow style
metadata: {version: 1.0, author: John}

Enabling flow style in serialization:

var settings = new YamlSerializerSettings 
{ 
    PreferFlowStyle = true,
    FlowStyleThreshold = 80  // Max line length
};

Comments are preserved during parsing when enabled:

# Database configuration
database:
  host: localhost  # Main server
  port: 5432
var settings = new YamlDeserializerSettings 
{ 
    PreserveComments = true 
};

Anchors (&) define reusable values, aliases (*) reference them:

# Define anchor
defaults: &defaults
  timeout: 30
  retries: 3

# Reference with alias
production:
  <<: *defaults      # Merge key
  host: prod.example.com

development:
  <<: *defaults
  host: dev.example.com

Example usage:

var yaml = @"
- &first item1
- second
- *first";

var list = YamlDeserializer.Deserialize<List<string>>(yaml);
// Result: ["item1", "second", "item1"]

Tags provide type hints in YAML documents:

# Built-in tags
name: !!str 123      # Force string
count: !!int "42"    # Force integer

# Custom tags
value: !custom data

The serializer automatically quotes strings when necessary:

var obj = new { 
    Message = "Hello: World",     // Contains colon
    Path = "C:\\Program Files",   // Contains backslash
    Multiline = "Line1\nLine2"    // Contains newline
};

// Output uses proper escaping:
// message: "Hello: World"
// path: "C:\\Program Files"
// multiline: "Line1\nLine2"

Escape sequences supported:

SequenceCharacter
\\Backslash
\"Double quote
\nNewline
\rCarriage return
\tTab
\0Null

Built-in naming conventions:

ConventionC# PropertyYAML Key
CamelCaseConventionFirstNamefirstName
PascalCaseConventionfirstNameFirstName
SnakeCaseConventionFirstNamefirst_name
KebabCaseConventionFirstNamefirst-name
OriginalCaseConventionFirstNameFirstName

Example:

var settings = new YamlSerializerSettings 
{ 
    NamingConvention = new SnakeCaseConvention() 
};

// C# property "UserName" becomes YAML key "user_name"

Implement IYamlTypeConverter for custom serialization:

public class VersionConverter : IYamlTypeConverter
{
    public bool CanConvert(Type type) => type == typeof(Version);

    public object? Read(object? value, Type type)
    {
        return value is string s ? Version.Parse(s) : null;
    }

    public void Write(/* ... */) { /* ... */ }
}

// Register converter
var converters = new List<IYamlTypeConverter> { new VersionConverter() };
var result = YamlDeserializer.Deserialize<Config>(yaml, extra: converters);

Exclude a property from serialization:

public class User
{
    public string Name { get; set; }
    
    [YamlIgnore]
    public string Password { get; set; }  // Never serialized
}

Customize the YAML key name:

public class Config
{
    [YamlProperty(Name = "api-key")]
    public string ApiKey { get; set; }  // Serialized as "api-key"
}

1. Reuse Serializer/Deserializer Instances

Section titled “1. Reuse Serializer/Deserializer Instances”
// Good - reuse instance
var serializer = new YamlSerializer(settings);
foreach (var item in items)
{
    var yaml = serializer.Serialize(item);
}

// Avoid - creating new instance each time
foreach (var item in items)
{
    var yaml = YamlSerializer.Serialize(item, settings); // Creates new instance
}
public class AppConfig
{
    public string Environment { get; set; }
    public DatabaseConfig Database { get; set; }
    public List<string> Features { get; set; }
}

// Load configuration
using var reader = new StreamReader("appsettings.yaml");
var config = YamlDeserializer.Deserialize<AppConfig>(reader);
var config = YamlDeserializer.Deserialize<Config>(yaml);

if (string.IsNullOrEmpty(config.ConnectionString))
    throw new InvalidOperationException("ConnectionString is required");
// Preferred - strongly typed
var config = YamlDeserializer.Deserialize<AppConfig>(yaml);

// Avoid - dynamic/dictionary when possible
var dict = YamlDeserializer.Deserialize<Dictionary<string, object>>(yaml);
// Static methods
static string Serialize(object? obj, YamlSerializerSettings? settings = null)

// Instance methods
YamlSerializer(YamlSerializerSettings? settings = null)
string Serialize(object? obj)
// Static methods
static T Deserialize<T>(string input, YamlDeserializerSettings? settings = null, 
    IEnumerable<IYamlTypeConverter>? extra = null)
static object? Deserialize(string input, Type t, ...)
static T Deserialize<T>(TextReader reader, ...)
static object? Deserialize(TextReader reader, Type t, ...)

// Instance methods
YamlDeserializer(YamlDeserializerSettings? settings = null, 
    IEnumerable<IYamlTypeConverter>? extra = null)
T Deserialize<T>(string input)
T Deserialize<T>(TextReader reader)
object? Deserialize(string input, Type t)
object? Deserialize(TextReader reader, Type t)

The library throws YamlException for parsing and conversion errors:

try
{
    var result = YamlDeserializer.Deserialize<Config>(invalidYaml);
}
catch (YamlException ex)
{
    Console.WriteLine($"YAML Error at line {ex.Line}, column {ex.Column}: {ex.Message}");
}

While Cortex.Serialization.Yaml supports many YAML features, some advanced capabilities are intentionally simplified:

  • Multi-document streams: Only single documents are supported
  • Binary data: Not directly supported (use base64 encoding)
  • Complex keys: Only scalar keys are supported in mappings
  • Circular references: Not detected (may cause stack overflow)

For these advanced scenarios, consider using a full YAML 1.2 compliant library.