Table of Contents

Built-in converter catalog

Every type that YamlSerializer handles without a user converter is served by a built-in converter. This page catalogs that set - which .NET types are provisioned, how each appears in YAML on write, and what the read path accepts. Resolution order and the rules for overriding a built-in with your own converter are in Writing converters.

YAML carries the JSON-compatible core scalar kinds - string, integer, float, Boolean, and null - so most everyday .NET types map without any converter at all. The writer emits block-style collections (an empty container falls back to flow [] / {}), and mappings preserve insertion order.

Scalars

.NET type YAML representation (write) Read accepts Notes
string string scalar string The scalar style (plain / quoted / block) is chosen by the writer and recorded on read by ScalarStyle.
char single-character string single-character string A source of any other length raises YamlSerializationException.
Guid string, canonical 36-character form any Guid.Parse-accepted form
Uri string, the original URI text string Relative and absolute URIs round-trip.
bool Boolean (true / false) Boolean Under SpecVersion = V1_1, the read path also accepts yes / no / on / off and y / n.
sbyte byte short ushort int uint long nint integer scalar integer Checked conversions; a value outside the target type raises YamlSerializationException.
ulong nuint Int128 UInt128 integer scalar, or the invariant decimal text outside the signed 64-bit range integer or string A value the writer's signed 64-bit integer surface cannot hold is emitted as its exact decimal text (quoted only when a leading sign would otherwise resolve it numerically) and converts back exactly on read.
double float scalar float NaN, +∞, and −∞ write as .nan, .inf, and -.inf.
float float scalar float Widens to double on write; narrows on read.
decimal quoted string of the exact invariant text float scalar or string Quoted because the exact text would otherwise resolve as a double; the quoting preserves the full decimal precision.
DateTime string, round-trip ("o") ISO-8601 string Read with DateTimeStyles.RoundtripKind. There is no native YAML timestamp type; the value travels as a string scalar.
DateTimeOffset string, round-trip ("o") ISO-8601 string As DateTime, preserving the offset.
TimeSpan string, the invariant TimeSpan form string
null the null scalar (null) null, ~, or the empty scalar A null member is omitted instead when DefaultIgnoreCondition requests it.
enum (any) string, the member name string (case-insensitive) or integer Integers instead of names when WriteEnumsAsStrings = false.
Note

decimal, DateTime, DateTimeOffset, and TimeSpan are not YAML core-schema kinds - there is no native timestamp or high-precision decimal scalar. The serializer carries each as a string (a decimal is additionally quoted so plain resolution does not turn it back into a double), and the read path parses that string with CultureInfo.InvariantCulture. They therefore round-trip exactly through Serialize / Deserialize, but a value typed object reads back as the underlying string, not as the original CLR type.

The YamlNumberHandling option governs the integer/float boundary on read: Strict (the default) rejects a non-integral or out-of-range float bound to an integer target, while AllowFloatToInteger truncates an integer-valued float toward zero. A plain integer scalar binds to a floating-point target under either policy. Integer scalars are resolved across the YAML radix forms - decimal, hexadecimal (0x), and the 0o octal prefix (plus YAML 1.1's leading-zero octal under SpecVersion = V1_1) - so 0xFF, 0o17, and 255 all bind to the same int.

Structural and document-model types

.NET type YAML representation Notes
arrays, List<T>, IEnumerable<T> and its interfaces, sets, and concrete collections with a parameterless constructor and Add sequence Block-style on write; an empty collection writes as flow []. On read the elements are bound into a List<T> and copied into the requested concrete type.
IDictionary<TKey,TValue> / IReadOnlyDictionary<TKey,TValue> and concrete dictionaries mapping Written in insertion order. Keys are stringified through Convert.ToString; on read a non-string key type is parsed back (enums by name, other keys via Convert.ChangeType). Mapping keys resolve to unique scalar strings (the Bodu YAML Core Tree Profile) - a duplicate stringified key on write raises YamlSerializationException.
plain classes and structs mapping The catch-all object converter, consulted last; properties first (reflection order), then public fields when IncludeFields is set. Read requires a public parameterless constructor and sets each writable member.
object-typed members the runtime type's form on write On read, an object target binds to a loosely-typed graph: a Dictionary<string, object?> for a mapping, a List<object?> for a sequence, and bool / long / double / string / null for scalars - not a YamlElement.
Nullable<T> the underlying value, or the null scalar A null scalar binds to null; otherwise the value binds as T.
YamlNode (and YamlObject / YamlArray / YamlValue) the node's own kind Mutable DOM bridge: Deserialize<YamlNode> materializes the value as a node tree (aliases and merge keys already resolved), and a node - standalone or as a member - writes its own kind.
YamlElement the element's own kind Read produces an element view backed by an internal document that shares the reader's row store - no disposal needed.
YamlDocument the document's root A deserialized document shares the reader's immutable row store; disposal is optional.

Fields

Properties map by default. Public fields participate when IncludeFields is true, following the same naming-policy, [PropertyName], and [Ignore] rules as properties:

public sealed class Counter
{
    public int Total { get; set; }
    public int Retries;   // included when IncludeFields is true
}

var options = new YamlSerializerOptions { IncludeFields = true };
string yaml = YamlSerializer.Serialize(new Counter { Total = 3, Retries = 1 }, options);
Total: 3
Retries: 1

Enums

By default an enum is written as its member-name string and read back case-insensitively (or as an integer). Set WriteEnumsAsStrings to false to write the underlying integer instead:

public enum Status { Active, OnHold }

string asString = YamlSerializer.Serialize(new { State = Status.OnHold });
// State: OnHold

var asInt = new YamlSerializerOptions { WriteEnumsAsStrings = false };
string asInteger = YamlSerializer.Serialize(new { State = Status.OnHold }, asInt);
// State: 1

On read an enum binds from a wire name (case-insensitively), or from an integer scalar (through Enum.ToObject), regardless of WriteEnumsAsStrings - the flag affects only the write side. Individual members rename on the wire with the shared StringEnumMemberNameAttribute, honored by the default handling and by the public enum converters:

  • YamlStringEnumConverter / YamlStringEnumConverter<TEnum> - member-name strings with an optional naming policy and an integers-on-read flag; register on the options for every enum, or reference the generic form from a [Converter(...)] attribute.
  • YamlNumberEnumConverter<TEnum> - the underlying numeric value as a YAML integer, regardless of WriteEnumsAsStrings.
var options = new YamlSerializerOptions();
options.Converters.Add(new YamlStringEnumConverter(NamingPolicy.SnakeCaseLower, allowIntegerValues: false));

// Status.OnHold now serializes as on_hold everywhere.

What YAML does not need a decision for

Unlike TOML, YAML has no decimal-handling or byte[]-handling option: decimal always travels as quoted exact text (above), and a byte[] is treated as an ordinary sequence of byte elements rather than a single encoded scalar. Anything outside the provisioned set - a value type rendered as one scalar, a type with a bespoke mapping shape, or a base64 byte[] - is the job of a custom converter.

Where to go next