---
title: "Migrating from YamlDotNet"
url: https://recursiveai.org/oneyaml/csharp/docs/migrate-yamldotnet/
description: "There are two ways to move code written against YamlDotNet:"
version: "1.2.1"
source: https://github.com/recursive-ai-oss/oneyaml-csharp/blob/7818048c4be434333767c47d66176fc626d1da8e/docs/migrate-yamldotnet.md
---

# Migrating from YamlDotNet

There are two ways to move code written against YamlDotNet:

1. **Swap to the adapter.** `OneYaml.Compat.YamlDotNet` provides the YamlDotNet serializer builders and representation model under new namespaces. For the API listed below, changing the package and the `using` lines keeps your call sites, and YAML 1.1 forms read as YamlDotNet reads them.
2. **Move to the `OneYaml` API.** `YamlSerializer` and `YamlDocument` are YAML 1.2, bounded for untrusted input, and can edit a file without losing its comments. This is the route for new code.

You can do the first now and the second file by file: the two packages work side by side.

## Before you swap

A build that passes after the swap is not yet a finished migration. Check these four things, in this order:

| Check | What to look for |
|---|---|
| **Compile gaps** | API the adapter does not provide fails to build: the scanner, `MergingParser`, type inspectors, node deserializers, event emitters, object-graph visitors, static contexts and comment events. The [full list](https://recursiveai.org/oneyaml/csharp/docs/compat-yamldotnet/#surface-not-provided-gap). |
| **Runtime rejections** | `Deserialize(IParser)` with a parser that is not the adapter's own `Parser` throws `NotSupportedException`. A serializer built with `EnsureRoundtrip()` throws `NotSupportedException` when `Serialize` is called: the adapter cannot do what it asks, so it says so instead of ignoring it. |
| **Semantic changes** | Serializer output can differ (more quoting, escapes), more valid YAML is read, the class of an exception can differ, and after you change a node `YamlStream.Save` writes without comments. See [Check after the swap](#check-after-the-swap) and the [list of differences](https://recursiveai.org/oneyaml/csharp/docs/compat-yamldotnet/). |
| **Safety limits** | The depth limit and the alias budget always apply, and `WithMaximumRecursion` cannot raise the depth above the library limit. For input you do not control, pass `YamlLimits.Untrusted` to `WithLimits` ([Input you do not control](#input-you-do-not-control)). `WithLimits` is an addition: YamlDotNet has no such setting. |

## Swap to the adapter

```sh
dotnet remove package YamlDotNet
dotnet add package OneYaml.Compat.YamlDotNet
```

| Before | After |
|---|---|
| `using YamlDotNet.Serialization;` | `using OneYaml.Compat.YamlDotNet.Serialization;` |
| `using YamlDotNet.Serialization.NamingConventions;` | `using OneYaml.Compat.YamlDotNet.Serialization.NamingConventions;` |
| `using YamlDotNet.RepresentationModel;` | `using OneYaml.Compat.YamlDotNet.RepresentationModel;` |
| `using YamlDotNet.Core;` | `using OneYaml.Compat.YamlDotNet.Core;` |

Before, with YamlDotNet 18.1.0:

<!-- sample: YamlDotNetBefore -->
```csharp
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\nlegacy: true\n");

ISerializer serializer = new SerializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .Build();
Console.Write(serializer.Serialize(service));
// name: web
// replicas: 2

class Service
{
    public string Name { get; set; } = "";
    public int Replicas { get; set; }
}
```

After, with the adapter; only the `using` lines changed, and the output is the same:

<!-- sample: YamlDotNetAfter -->
```csharp
using OneYaml.Compat.YamlDotNet.Serialization;
using OneYaml.Compat.YamlDotNet.Serialization.NamingConventions;

IDeserializer deserializer = new DeserializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .IgnoreUnmatchedProperties()
    .Build();
var service = deserializer.Deserialize<Service>("name: web\nreplicas: 2\nlegacy: true\n");

ISerializer serializer = new SerializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .Build();
Console.Write(serializer.Serialize(service));
// name: web
// replicas: 2

class Service
{
    public string Name { get; set; } = "";
    public int Replicas { get; set; }
}
```

### What the adapter covers

- **`Serialization`:** `DeserializerBuilder` and `SerializerBuilder` with `WithNamingConvention`, `WithEnumNamingConvention`, `WithTypeConverter`, `WithTagMapping`, `WithAttributeOverride`, `IgnoreUnmatchedProperties`, `WithCaseInsensitivePropertyMatching`, `WithDuplicateKeyChecking`, `WithAttemptingUnquotedStringTypeDeserialization`, `WithMaximumRecursion`, `WithTypeMapping`, `WithObjectFactory`, `ConfigureDefaultValuesHandling`, `WithQuotingNecessaryStrings`, `WithDefaultScalarStyle`, `DisableAliases`, `JsonCompatible`, `WithNewLine` and `WithIndentedSequences`; `IDeserializer`, `ISerializer`, the six naming conventions, `[YamlMember]`, `[YamlIgnore]`, `[YamlConverter]`, `IYamlTypeConverter` and `IYamlConvertible`.
- **`RepresentationModel`:** `YamlStream`, `YamlDocument`, `YamlMappingNode`, `YamlSequenceNode`, `YamlScalarNode`, visitors, node positions.
- **`Core`:** `Parser`, `Emitter`, the event classes, `Mark` and the exception types.

The [list of differences](https://recursiveai.org/oneyaml/csharp/docs/compat-yamldotnet/#surface-not-provided-gap) names what is not provided, such as the scanner, `MergingParser`, type inspectors, node deserializers and static contexts. Code that uses them does not compile, so the gap shows at build time.

The adapter's representation model reads a file and writes it back as it was when nothing changed:

<!-- sample: YamlDotNetModel -->
```csharp
using OneYaml.Compat.YamlDotNet.RepresentationModel;

var stream = new YamlStream();
stream.Load(new StringReader("# service settings\nname: web\nreplicas: 2   # set by CI\n"));

var root = (YamlMappingNode)stream.Documents[0].RootNode;
Console.WriteLine(root["replicas"]); // 2

var text = new StringWriter();
stream.Save(text);               // nothing was changed, so the text comes back as it was read
Console.Write(text);
// # service settings
// name: web
// replicas: 2   # set by CI
```

After you change a node, `Save` writes the model again without comments, as YamlDotNet does. To change a file and keep its comments, use `YamlDocument` ([below](#editing-a-file)).

### Check after the swap

The adapter reproduces YamlDotNet's documented behaviour, not its defects. Read [the full list of differences](https://recursiveai.org/oneyaml/csharp/docs/compat-yamldotnet/), each with a repro, and check these first:

- **Output that reads back.** Strings that a reader would take for something else are quoted (`'true'`, `'yes'`, `'123'`), characters that YamlDotNet writes in a way that changes on reading are escaped, and long keys are written so that they read back. Snapshot tests of serializer output may need updating.
- **Comments survive an unchanged round trip.** `YamlStream.Save` returns the loaded text when nothing changed; YamlDotNet writes it again without comments and with `...` after each document.
- **More valid YAML is read.** Some inputs that YamlDotNet refuses or throws `ArgumentException` on are valid YAML 1.2 and are read.
- **Limits stay on.** The deserializer enforces the alias budget and the depth limit. An alias into `object` or a collection gives back the same instance, and every use is charged to the alias budget as if it were expanded, so a billion-laughs input fails with a `YamlException`. For input you do not control, call `WithLimits(YamlLimits.Untrusted)` on the builder, or pass `YamlLimits.Untrusted` to `YamlStream.Load` or `new Parser(reader, limits)` ([Input you do not control](#input-you-do-not-control)).
- **Exception classes.** `SyntaxErrorException`, `SemanticErrorException`, `MaximumRecursionLevelReachedException` and `AnchorNotFoundException` are raised, but which class a given error gets can differ from YamlDotNet. Catch `YamlException` if you can.
- **Line breaks.** The serializer writes the platform line break, as YamlDotNet does.

### Input you do not control

The adapter adds three members that YamlDotNet does not have, so that reading can keep to the [limits](https://recursiveai.org/oneyaml/csharp/docs/limits/) of the `OneYaml` library: `DeserializerBuilder.WithLimits(YamlLimits)`, `YamlStream.Load(TextReader, YamlLimits)` and `new Parser(TextReader, YamlLimits)`. Without them the adapter reads with `YamlLimits.Default`. For uploads, request bodies and files from other systems, pass `YamlLimits.Untrusted`, which also bounds the input size, the size of a scalar and the number of documents. `YamlLimits` is in the `OneYaml` namespace; import it with `using YamlLimits = OneYaml.YamlLimits;`, not `using OneYaml;`, because that namespace has its own `YamlException`, `YamlDocument`, `YamlNode` and `YamlIgnoreAttribute`, and importing it beside the adapter's namespaces makes those names ambiguous (error CS0104).

<!-- sample: YamlDotNetLimits -->
```csharp
using OneYaml.Compat.YamlDotNet.Core;
using OneYaml.Compat.YamlDotNet.Serialization;
using YamlLimits = OneYaml.YamlLimits;

string upload = new string('[', 129) + new string(']', 129); // nested 129 deep

// WithLimits is not in YamlDotNet. It takes the limits of the OneYaml library; Untrusted is its preset for outside input.
IDeserializer deserializer = new DeserializerBuilder()
    .WithLimits(YamlLimits.Untrusted)
    .Build();
try
{
    deserializer.Deserialize<object>(upload);
}
catch (YamlException e)
{
    Console.WriteLine(e.Message); // The nesting depth is larger than the limit MaxDepth (128). (line 1, column 129)
}

// The default limits allow the same text.
Console.WriteLine(new DeserializerBuilder().Build().Deserialize<object>(upload) is not null); // True
```

Code that uses these members no longer compiles against YamlDotNet itself.

## Move to the OneYaml API

```sh
dotnet add package OneYaml
```

If the project still references `OneYaml.Compat.YamlDotNet`, keep `OneYaml` at the same version: the adapter depends on that exact version.

<!-- sample: YamlDotNetPrimary -->
```csharp
using OneYaml;

// camelCase keys and skipping unknown keys are the defaults; no builder is needed.
var service = YamlSerializer.Deserialize<Service>("name: web\nreplicas: 2\nlegacy: true\n")!;
Console.Write(YamlSerializer.Serialize(service));
// name: web
// replicas: 2

class Service
{
    public string Name { get; set; } = "";
    public int Replicas { get; set; }
}
```

| YamlDotNet | OneYaml |
|---|---|
| `new DeserializerBuilder().Build().Deserialize<T>(yaml)` | `YamlSerializer.Deserialize<T>(yaml)` |
| `new SerializerBuilder().Build().Serialize(value)` | `YamlSerializer.Serialize(value)` |
| `WithNamingConvention(CamelCaseNamingConvention.Instance)` | the default (`PropertyNamingPolicy = JsonNamingPolicy.CamelCase`) |
| `UnderscoredNamingConvention`, `HyphenatedNamingConvention` | `JsonNamingPolicy.SnakeCaseLower`, `JsonNamingPolicy.KebabCaseLower` |
| `NullNamingConvention` (YamlDotNet's default) | `PropertyNamingPolicy = null` |
| `IgnoreUnmatchedProperties()` | the default; `UnmappedMemberHandling = Disallow` is YamlDotNet's default behaviour |
| `WithCaseInsensitivePropertyMatching()` | `PropertyNameCaseInsensitive = true` |
| `WithDuplicateKeyChecking()` | always on: a repeated key is an error |
| `WithMaximumRecursion(n)` | `Limits = YamlLimits.Default with { MaxDepth = n }` |
| `ConfigureDefaultValuesHandling(OmitNull)` | `DefaultIgnoreCondition = YamlIgnoreCondition.WhenWritingNull` |
| `WithTypeConverter(IYamlTypeConverter)` | `Converters = { new MyConverter() }`, a `YamlConverter<T>` ([Converters](https://recursiveai.org/oneyaml/csharp/docs/serializer/#converters)) |
| `WithTagMapping("!x", typeof(X))` | `[YamlPolymorphic(DiscriminatorKind = YamlDiscriminatorKind.Tag)]` and `[YamlDerivedType(typeof(X), "x")]` on a base type ([Polymorphism](https://recursiveai.org/oneyaml/csharp/docs/serializer/#polymorphism)) |
| `[YamlMember(Alias = "x")]`, `[YamlMember(Order = n)]` | `[YamlPropertyName("x")]`, `[YamlPropertyOrder(n)]` |
| `[YamlIgnore]` | `[YamlIgnore]` (in the `OneYaml` namespace) |
| `MergingParser` | `MergeKeys = true` |
| `YamlStream`, `YamlMappingNode`, `YamlScalarNode` | `YamlDocument`, `YamlNode` ([Editing](https://recursiveai.org/oneyaml/csharp/docs/editing/)) |
| `YamlException.Start` | `YamlException.Line`, `Column`, `Offset`, plus `Category` and `Path` |
| static builders with a separate generator package | `[YamlSerializable]` on a `YamlSerializerContext`; the generator is in the package ([Source generation](https://recursiveai.org/oneyaml/csharp/docs/serializer/#source-generation-and-native-aot)) |

Behaviour to expect when you move:

- **YAML 1.2, not 1.1.** `yes`, `on` and `y` are strings, so reading `enabled: yes` into a `bool` is an error; write `true`. `017` is 17, not octal 15; `0o17` is 15.
- **Untyped values have types.** `Deserialize<object>` gives `long`, `double`, `bool` and `null` for plain scalars, where YamlDotNet gives strings ([Untyped values](https://recursiveai.org/oneyaml/csharp/docs/serializer/#untyped-values)).
- **Repeated keys are an error**, where YamlDotNet keeps the last one by default.
- **`null` members are written** as `null` unless you set `DefaultIgnoreCondition`.

## Editing a file

YamlDotNet's representation model writes a file again from the model, so comments and blank lines are lost when you save a change. `YamlDocument` changes only the value you set:

<!-- sample: YamlDotNetStreamToDocument -->
```csharp
// Instead of YamlStream: load, change one value, save, and keep the comments.
using var doc = YamlDocument.Parse("# service settings\nname: web\nreplicas: 2   # set by CI\n");
doc.Root["replicas"] = 3;
Console.Write(doc);
// # service settings
// name: web
// replicas: 3   # set by CI
```

See [Editing](https://recursiveai.org/oneyaml/csharp/docs/editing/) for pointers, styles and aliases, and [Patches](https://recursiveai.org/oneyaml/csharp/docs/patches/) to apply a JSON Patch.
