To use YAML in a C# native AOT app, bind it through source-generated metadata instead of reflection. Native AOT trims away the members that a reflection-based serializer reads at run time, so a plain Deserialize<T>(yaml) call warns at publish time and can fail once published. With OneYaml you declare a YamlSerializerContext, list your root types with [YamlSerializable], and pass the generated metadata to YamlSerializer. The generator ships inside the OneYaml package, the pattern is the one System.Text.Json uses, and YamlDocument edits that keep comments publish without warnings too. The code on this page uses OneYaml 1.2.1 on .NET 10 and was published with native AOT for linux-x64.
1. Turn on native AOT and add the package
dotnet new console -f net10.0 dotnet add package OneYaml --version 1.2.1
<PropertyGroup> <TargetFramework>net10.0</TargetFramework> <PublishAot>true</PublishAot> </PropertyGroup>
OneYaml targets .NET 8 and .NET 10, the two runtimes it supports (stability). There is no second package to add for the generator.
2. Declare a context and pass its metadata
using OneYaml; string yaml = """ name: billing-api replicas: 3 endpoint: https://billing.internal.example labels: tier: backend team: payments """; ServiceConfig config = YamlSerializer.Deserialize(yaml, AppYamlContext.Default.ServiceConfig)!; Console.WriteLine($"{config.Name}: {config.Replicas} replicas at {config.Endpoint.Host}"); Console.WriteLine($"labels: {string.Join(", ", config.Labels.Select(l => $"{l.Key}={l.Value}"))}"); Console.Write(YamlSerializer.Serialize(config with { Replicas = 5 }, AppYamlContext.Default.ServiceConfig)); record ServiceConfig(string Name, int Replicas, Uri Endpoint, Dictionary<string, string> Labels); [YamlSerializable(typeof(ServiceConfig))] partial class AppYamlContext : YamlSerializerContext;
billing-api: 3 replicas at billing.internal.example labels: tier=backend, team=payments name: billing-api replicas: 5 endpoint: https://billing.internal.example labels: tier: backend team: payments
The generator writes a YamlTypeInfo<T> property on the context for each [YamlSerializable] type and for every type it reaches, so Dictionary<string, string> and Uri need no attribute of their own. Records with constructor parameters bind as they do with reflection, and so do the System.Text.Json attributes, converters and polymorphism (serializer). For options other than the defaults, such as a naming policy, create the context with them: new AppYamlContext(new YamlSerializerOptions { ... }).
3. Publish
dotnet publish -c Release -r linux-x64The publish finished with no trim or AOT warnings, and the output is one native executable that prints the same lines as above. On linux-x64 with .NET SDK 10.0.400 it was 2,377,208 bytes, plus a separate .dbg symbols file. Your size will differ with your code and target.
What happens if you call the reflection overload
The overloads that take YamlSerializerOptions use reflection, and OneYaml marks them [RequiresUnreferencedCode] and [RequiresDynamicCode], so the compiler tells you before anything breaks. Here is the same app written the reflection way:
using OneYaml; var config = YamlSerializer.Deserialize<ServiceConfig>("name: billing-api\nreplicas: 3\n"); Console.WriteLine($"{config!.Name}: {config.Replicas} replicas"); record ServiceConfig(string Name, int Replicas);
It runs under dotnet run, but dotnet publish reports both warnings (shown here without the directory prefix and the project-file suffix):
Program.cs(3,14): warning IL2026: Using member 'OneYaml.YamlSerializer.Deserialize<T>(String, YamlSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Reflection-based binding reads members and constructors that trimming can remove. Pass a YamlTypeInfo from a YamlSerializerContext instead. Program.cs(3,14): warning IL3050: Using member 'OneYaml.YamlSerializer.Deserialize<T>(String, YamlSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. Reflection-based binding makes generic types and compiles accessors at run time. Pass a YamlTypeInfo from a YamlSerializerContext instead.
Run the published binary and the call fails at once, with a message that says what to do, rather than returning an object with missing members:
Unhandled exception. System.InvalidOperationException: Reflection-based binding is turned off in this application. Pass generated metadata from a YamlSerializerContext, set TypeInfoResolver, or set the OneYaml.YamlSerializer.IsReflectionEnabledByDefault switch to true.Reflection-based binding is off by default in apps published with PublishTrimmed or PublishAot. The MSBuild property YamlSerializerIsReflectionEnabledByDefault turns it back on, but only do that if you have kept the members it needs. Mistakes in a context, such as a context that isn’t partial, are build errors OY0001 to OY0010 (serializer).
Edit YAML files in an AOT app
Many AOT apps are command-line tools, and tools often change a config file rather than just read it. YamlDocument changes the values you set and keeps every comment and the rest of the layout. Setting strings and numbers needs no context at all:
using OneYaml; string yaml = """ # Deployed by the release job image: repository: registry.example.com/billing-api tag: 1.4.2 # bump on release replicas: 3 """; using var doc = YamlDocument.Parse(yaml); doc.Set("/image/tag", "1.5.0"); doc.Root["replicas"] = 5; Console.WriteLine(doc); string tag = doc.Root["image"]!["tag"]!.GetValue<string>(); Console.WriteLine($"tag is now {tag}");
# Deployed by the release job image: repository: registry.example.com/billing-api tag: 1.5.0 # bump on release replicas: 5 tag is now 1.5.0
This app also published for native AOT with no warnings, and the binary prints the output above. To write a whole object into a document, YamlDocument has generated-metadata overloads: Set(path, value, typeInfo), Get(path, typeInfo) and YamlNode.Deserialize(typeInfo) (editing).
YamlDotNet and SharpYaml with native AOT
| Library | Generator | How you declare it | Source |
|---|---|---|---|
| OneYaml 1.2.1 | In the OneYaml package | partial class : YamlSerializerContext with [YamlSerializable]; pass Context.Default.T | serializer docs; publish measured above |
| YamlDotNet 18.1.0 | Separate package, Vecc.YamlDotNet.Analyzers.StaticGenerator 18.1.0, “a static context for use with YamlDotNet to support ahead-of-time and library trimming” | partial class : StaticContext with [YamlStaticContext] and [YamlSerializable]; build with StaticDeserializerBuilder | NuGet; issue #740 (closed as completed, March 2023); publish measured below |
| SharpYaml 3.16.0 | In the SharpYaml package (“no extra package needed”) | partial class : YamlSerializerContext with [YamlSerializable]; reflection is off by default under PublishAot | README, source generation |
YamlDotNet’s wiki doesn’t cover native AOT, so this sample uses the public types of the two published packages. It ran, and it published for native AOT on linux-x64 with no warnings:
using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; string yaml = """ name: billing-api replicas: 3 labels: tier: backend team: payments """; IDeserializer deserializer = new StaticDeserializerBuilder(new AppStaticContext()) .WithNamingConvention(CamelCaseNamingConvention.Instance) .Build(); ServiceConfig config = deserializer.Deserialize<ServiceConfig>(yaml); Console.WriteLine($"{config.Name}: {config.Replicas} replicas, {config.Labels.Count} labels"); public class ServiceConfig { public string Name { get; set; } = ""; public int Replicas { get; set; } public Dictionary<string, string> Labels { get; set; } = new(); } [YamlStaticContext] [YamlSerializable(typeof(ServiceConfig))] public partial class AppStaticContext : StaticContext { }
billing-api: 3 replicas, 2 labelsIf you are moving an AOT app from YamlDotNet, note that the OneYaml.Compat.YamlDotNet package doesn’t cover YamlDotNet’s static contexts. Switch those call sites to a YamlSerializerContext as shown above. Replacing YamlDotNet covers the rest of a migration.
Where to go next
The short version of this page is the cookbook recipe use YAML in a native AOT app. To pick a library first, see choosing a C# YAML library; to change files without losing comments, edit YAML without losing comments. Install and first steps: getting started with OneYaml. Versions on this page are the latest published on 26 October 2026.