Meziantou.Framework.Yaml
2.0.12
Prefix Reserved
dotnet add package Meziantou.Framework.Yaml --version 2.0.12
NuGet\Install-Package Meziantou.Framework.Yaml -Version 2.0.12
<PackageReference Include="Meziantou.Framework.Yaml" Version="2.0.12" />
<PackageVersion Include="Meziantou.Framework.Yaml" Version="2.0.12" />
<PackageReference Include="Meziantou.Framework.Yaml" />
paket add Meziantou.Framework.Yaml --version 2.0.12
#r "nuget: Meziantou.Framework.Yaml, 2.0.12"
#:package Meziantou.Framework.Yaml@2.0.12
#addin nuget:?package=Meziantou.Framework.Yaml&version=2.0.12
#tool nuget:?package=Meziantou.Framework.Yaml&version=2.0.12
Meziantou.Framework.Yaml
Meziantou.Framework.Yaml is a YAML parser, emitter, and serializer for .NET. It reads and writes YAML documents,
serializes object graphs, deserializes typed models, and generates serialization metadata at compile time for
NativeAOT and trimming scenarios.
The source generator ships inside the package. No additional package is required to use generated
YamlSerializerContext types.
Install the package
dotnet add package Meziantou.Framework.Yaml
Table of contents
- Overview
- Serialize and deserialize objects
- Supported types
- Configure serialization
- Attributes
- Lifecycle callbacks
- Custom converters
- Polymorphism
- C# unions
- Type classifiers
- Anchors, aliases, and merge keys
- Schemas
- Source generation
- NativeAOT and trimming
- Document Object Model
- Syntax tree
- Parser and emitter
- Error handling
- Hardening untrusted input
Overview
The package targets YAML 1.2. The parser accepts documents declaring %YAML 1.1, %YAML 1.2, or a higher minor
version, using YAML 1.2 rules. Other major versions and YAML 1.0 are rejected with a SemanticErrorException.
Multi-document streams, %TAG directives, block and flow
collections, all five scalar styles (plain, single-quoted, double-quoted, literal, folded), anchors, aliases, merge
keys, tags, and comments are all parsed.
Four API layers are available, from the lowest level to the highest:
| Layer | Entry points | Use it for |
|---|---|---|
| Tokens and events | Scanner<TBuffer>, Parser<TBuffer>, EventReader, Emitter |
Streaming over parsing events without materializing a tree |
| Syntax tree | YamlSyntaxTree |
Lossless representation, including comments and whitespace, with source spans |
| Document Object Model | YamlStream, YamlDocument, YamlMapping, YamlSequence, YamlValue |
Inspecting or transforming a document without binding to a CLR type |
| Serializer | YamlSerializer, YamlReader, YamlWriter |
Binding YAML to CLR types |
Serialize and deserialize objects
YamlSerializer and YamlSerializerOptions are in the Meziantou.Framework.Yaml namespace, and the attributes in
Meziantou.Framework.Yaml.Serialization. Given this model:
public sealed class Product
{
public int Id { get; set; }
[YamlPropertyName("name")]
public string DisplayName { get; set; } = "";
public string[] Tags { get; set; } = [];
}
Serialize writes an instance as YAML, and Deserialize reads it back:
var options = new YamlSerializerOptions
{
PropertyNamingPolicy = YamlNamingPolicy.KebabCaseLower,
};
var yaml = YamlSerializer.Serialize(new Product
{
Id = 1,
DisplayName = "Sample product",
Tags = ["new", "featured"],
}, options);
Assert.Equal("""
id: 1
name: Sample product
tags:
- new
- featured
""", yaml);
var product = YamlSerializer.Deserialize<Product>(yaml, options);
Assert.Equal("Sample product", product!.DisplayName);
YamlSerializer reads from a string, a TextReader (streaming), or a UTF-8 Stream, and writes to a string, a
TextWriter, a UTF-8 Stream, or an IBufferWriter<char>. Every overload has a generic and a Type-based form, and
accepts either a YamlSerializerOptions, a YamlSerializerContext, or a YamlTypeInfo.
TryDeserialize returns false instead of throwing when the payload cannot be bound:
if (YamlSerializer.TryDeserialize<Product>("Id: 1", out var value))
{
Assert.Equal(1, value!.Id);
}
Assert.False(YamlSerializer.TryDeserialize<Product>("Id: not a number", out _));
When a stream contains several documents, YamlSerializer.Deserialize binds the first one. Use
YamlStream to read every document.
Supported types
| Category | Types |
|---|---|
| Text | string, char, Rune |
| Boolean | bool |
| Integers | byte, sbyte, short, ushort, int, uint, long, ulong, nint, nuint, Int128, UInt128, BigInteger |
| Floating point | float, double, decimal, Half |
| Date and time | DateTime, DateTimeOffset, DateOnly, TimeOnly, TimeSpan |
| Other scalars | Guid, Uri, CultureInfo, Version, enums |
| net11.0 only | BFloat16, Decimal32, Decimal64, Decimal128 |
| Collections | arrays, List<>, HashSet<>, ImmutableArray<>, ImmutableList<>, ImmutableHashSet<>, FrozenSet<>, Queue<>, Stack<>, ConcurrentQueue<>, ConcurrentStack<>, ConcurrentBag<>, ArraySegment<>, IEnumerable<>, ICollection<>, IList<>, IReadOnlyCollection<>, IReadOnlyList<>, ISet<>, IReadOnlySet<> |
| Dictionaries | Dictionary<,>, OrderedDictionary<,>, FrozenDictionary<,>, IDictionary<,>, IReadOnlyDictionary<,>, with string or non-string keys |
| Pairs | KeyValuePair<,>, as a mapping with a Key and a Value entry |
| Complex numbers | Complex, and on net11.0 Complex<T> (with float, double, Half, BFloat16 or Decimal* parts), as a mapping with a Real and an Imaginary entry |
| Untyped | object, object[], List<object>, Dictionary<string, object> |
| Documents | any YamlNode (YamlMapping, YamlSequence, YamlValue, …) |
| Unions | C# unions |
Every value type in the table also works as its Nullable<T> counterpart. Any type implementing a single
IDictionary<TKey, TValue> and exposing a parameterless constructor (SortedDictionary<,>, ConcurrentDictionary<,>,
a class deriving from Dictionary<,>, …) is treated as a dictionary. Any other type implementing a single
ICollection<T> and exposing a parameterless constructor is treated as a collection. Anything else is serialized as a
mapping of its properties, using the object contract described below.
A dictionary key is written as a scalar, so its type must be string, object, an enum, or one of the scalar types of
the table. A key of any other type, which could not be read back, throws a NotSupportedException (the source generator
reports MFY002 or MFY007), unless a custom converter handles the key type. The keys of a dictionary whose key type is
object keep their type: a string key such as "1" or "true" is quoted so it is not read back as a number or a
boolean, and YamlWriter.WriteDictionaryKey writes a key the same way from a custom converter.
Reading into object produces null, bool, long (or ulong beyond its range), double, string, List<object?>
for a sequence, and Dictionary<object, object?> for a mapping. The keys of that dictionary are resolved like values, so
1 is a long and '1' is a string, and both can appear in the same mapping. Two keys that resolve to the same value,
such as 1 and 0x1, are duplicates. A null key (~, null) and a key that is itself a sequence or a mapping cannot be
represented and throw a YamlException.
A key of one of the supported key types is always written and read as a scalar by the dictionary itself. A custom
converter registered for its type, in Converters, in YamlSourceGenerationOptionsAttribute.Converters, or with
YamlConverterAttribute on the type, applies to the values of the dictionary, not to its keys.
The other collections of the base class library, such as ArrayList, Hashtable, ReadOnlyCollection<>, or the
immutable collections not listed above, and the types that cannot be serialized at all, such as Type, delegates, and
IAsyncEnumerable<>, are not written as a mapping of their properties, which could not be read back: they throw a
NotSupportedException, and the source generator reports them.
A stack (Stack<>, ConcurrentStack<>) is written from its top and read back in the same order. ArraySegment<> is
written as the elements of the segment, a default segment as null, and is read over a new array. KeyValuePair<,>
entry names follow PropertyNamingPolicy. BigInteger is written as an integer, Version using its string
representation, and Rune as a one-character scalar.
Enums are written using their member name and read case-insensitively, with a numeric fallback. Use
YamlEnumMemberNameAttribute to control the name of an individual member.
Apart from a string written with PreferQuotedForAmbiguousScalars disabled, a value that is not null is never written as a
null scalar: an enum member named Null, a relative Uri such as ~, a dictionary key, or a custom converter calling
YamlWriter.WriteScalar("null") are quoted. Their text is still written plain when it reads as a boolean or a number
(an enum member named True, the Version 1.2), which a typed member reads back unchanged, but which an object
member or a union resolves as a boolean or a number.
Uri is written using its original string and read with UriKind.RelativeOrAbsolute, so both absolute and relative
URIs round-trip. CultureInfo is written using its culture name (CultureInfo.InvariantCulture writes an empty
name), and read back through CultureInfo.GetCultureInfo.
Object contracts support parameterless constructors, parameterized constructors, records, init accessors, the
required keyword, and non-public members annotated with YamlIncludeAttribute. When a type declares several public
constructors, annotate the one to use with YamlConstructorAttribute. A struct is created without calling a constructor
unless one is annotated with YamlConstructorAttribute.
A null scalar, such as ~, assigns null to a member whose type accepts it. A custom converter of the member reads the
null scalar itself, and a non-nullable value type rejects it, as it does for a root value or a collection element:
Value: ~ throws a YamlException for an int or a struct member instead of assigning its default value.
Members are listed from the base-most type down to the type itself and, within a type, properties come before fields,
each in declaration order. A member that overrides or hides a member of a base type takes its position, and inherits the
attributes of the member it overrides. Two members mapping to the same YAML name throw an InvalidOperationException.
Configure serialization
YamlSerializerOptions is a record, so an existing instance can be adapted with with. YamlSerializerOptions.Default
exposes the default instance.
Naming
| Option | Default | Description |
|---|---|---|
PropertyNamingPolicy |
null |
Converts CLR member names. Built-in policies: YamlNamingPolicy.CamelCase, SnakeCaseLower, SnakeCaseUpper, KebabCaseLower, KebabCaseUpper, PascalCase. Derive from YamlNamingPolicy for a custom policy. |
DictionaryKeyPolicy |
null |
Converts dictionary keys when writing. |
PropertyNameCaseInsensitive |
false |
Matches mapping keys to members ignoring case. |
Member selection
| Option | Default | Description |
|---|---|---|
IncludeFields |
false |
Includes public fields. |
IgnoreReadOnlyFields |
false |
Skips read-only fields when writing. |
IgnoreReadOnlyProperties |
false |
Skips get-only properties when writing. |
DefaultIgnoreCondition |
Never |
Never, WhenWritingNull, WhenWritingDefault, Always, WhenWriting, WhenReading. |
Output shape
| Option | Default | Description |
|---|---|---|
WriteIndented |
true |
Writes block collections indented by IndentSize. When disabled, collections use the flow style and the document stays on a single line. |
IndentSize |
2 |
Number of spaces per indentation level. Ignored when WriteIndented is disabled. |
IndentBlockSequences |
true |
Indents a block sequence that is the value of a mapping key. When disabled, the sequence dashes stay at the indentation of the parent mapping. |
MappingOrder |
Declaration |
Declaration or Sorted. |
BlockSequenceMappingStyle |
Compact |
How a mapping inside a block sequence is emitted. |
BlockSequenceSequenceStyle |
Expanded |
How a nested sequence inside a block sequence is emitted. |
ScalarStylePreferences |
PreferPlainStyle = true, PreferQuotedForAmbiguousScalars = true, StringStyle = Any |
Quotes scalars that would otherwise resolve to a boolean, a number, or null, and selects the style used for string values. PreferPlainStyle = false double-quotes every string value and mapping key the writer would otherwise write plain, unless StringStyle requests another style; a dictionary key that is not a string and reads as a number or a boolean stays plain, so that it keeps its type. |
BlockSequenceMappingStyle controls whether the first key of a mapping shares the line of its sequence dash:
# Compact
items:
- id: 1
name: first
# Expanded
items:
-
id: 1
name: first
IndentBlockSequences controls whether a sequence used as a mapping value gets its own indentation level:
# IndentBlockSequences = true
product:
tags:
- new
- sale
# IndentBlockSequences = false
product:
tags:
- new
- sale
A sequence nested inside another sequence is always indented past its parent dash, because YAML has no unindented form for it.
Block YAML expresses nesting through indentation, so WriteIndented = false switches collections to the flow style instead of writing block collections without indentation. The whole document stays on a single line, and IndentSize, BlockSequenceMappingStyle, and BlockSequenceSequenceStyle no longer apply:
# WriteIndented = true, IndentSize = 2
product:
name: Table
tags:
- new
# WriteIndented = false
{product: {name: Table, tags: [new]}}
Flow output is read back by the deserializer like any other YAML, and scalars containing a flow indicator such as ,, :, [, or { are quoted so the round-trip stays faithful.
StringStyle selects the style used for string values. The default, Any, lets the writer choose between the plain
and the double-quoted style. Literal writes a block scalar instead:
var options = new YamlSerializerOptions
{
ScalarStylePreferences = new YamlScalarStylePreferences { StringStyle = ScalarStyle.Literal },
};
var yaml = YamlSerializer.Serialize(new { Script = "echo one\necho two\n" }, options);
Script: |
echo one
echo two
The chomping indicator follows the value: |- when it has no trailing line break, | for one, and |+ for more.
Folded writes > instead, and Plain, SingleQuoted, and DoubleQuoted ask for those styles.
The requested style is used only when the value round-trips through it. A block scalar cannot represent an empty value, a carriage return, a control character, a line that ends with a blank, or a line that starts with a tab, and none can be written inside a flow collection; a folded scalar additionally cannot hold a line that starts with a space. When the style does not fit, the value falls back to the automatic style. The style applies to string values only — mapping keys and non-string scalars such as numbers, booleans, and dates keep their own representation.
[YamlStringStyle] overrides the style for one member and for the strings below it:
internal sealed class Job
{
[YamlStringStyle(ScalarStyle.Literal)]
public string? Script { get; set; }
public string? Name { get; set; }
}
Script: |-
echo one
echo two
Name: build
PreferQuotedForAmbiguousScalars keeps a round-trip faithful when a string looks like another type:
var yaml = YamlSerializer.Serialize(new { A = "yes", B = "123", C = "plain" });
A: yes
B: "123"
C: plain
Reading
| Option | Default | Description |
|---|---|---|
UnmappedMemberHandling |
Skip |
Disallow throws when a mapping key matches no member. |
RespectRequiredConstructorParameters |
true |
Requires a value for every non-optional constructor parameter. |
RespectNullableAnnotations |
true |
Rejects null for a member declared non-nullable. |
PreferredObjectCreationHandling |
Replace |
Populate adds to the collection or object already held by the member instead of replacing it. |
DuplicateKeyHandling |
Error |
FirstWins or LastWins accept duplicate mapping keys. |
SourceName |
null |
Name (typically a file path) used to annotate exception messages. |
Metadata and extensibility
| Option | Default | Description |
|---|---|---|
Converters |
empty | Custom converters, evaluated in order, ahead of built-in converters. |
TypeClassifiers |
empty | Factories selecting a union case or a derived type from the payload. |
TypeInfoResolver |
null |
Metadata source; set it to a generated YamlSerializerContext to avoid reflection. |
PolymorphismOptions |
see Polymorphism | Discriminator style, property name, unknown-type handling, runtime mappings. |
ReferenceHandling |
None |
See Anchors, aliases, and merge keys. |
Schema / UseSchema |
Core / false |
See Schemas. |
GetTypeInfo<T>() and TryGetTypeInfo<T>() return the contract metadata an options instance resolves for a type.
Attributes
| Attribute | Target | Description |
|---|---|---|
YamlPropertyNameAttribute |
member | Sets the YAML key. Takes precedence over every naming policy. |
YamlNamingPolicyAttribute |
type, member | Overrides PropertyNamingPolicy. |
YamlPropertyOrderAttribute |
member | Orders emitted keys. |
YamlIgnoreAttribute |
type, member | Overrides DefaultIgnoreCondition. |
YamlIncludeAttribute |
member | Includes a non-public or otherwise excluded member. |
YamlRequiredAttribute |
member | Requires the key to be present when reading. |
YamlConstructorAttribute |
constructor | Selects the deserialization constructor, including a non-public one. |
YamlConverterAttribute |
type, member | Uses a custom converter. |
YamlExtensionDataAttribute |
member | Collects unmatched mapping keys. |
YamlEnumMemberNameAttribute |
enum member | Sets the YAML value of an enum member. |
YamlNumberHandlingAttribute |
type, member | AllowReadingFromString, WriteAsString, AllowNamedFloatingPointLiterals. |
YamlObjectCreationHandlingAttribute |
type, member | Replace or Populate. |
YamlUnmappedMemberHandlingAttribute |
type | Skip or Disallow for this type. |
YamlBlockSequenceItemStyleAttribute |
type, member | Block sequence item style for this type or member. |
YamlPolymorphicAttribute |
type | Enables and configures polymorphism. |
YamlDerivedTypeAttribute |
type | Registers a derived type on its base type. |
YamlDerivedTypeMappingAttribute |
assembly, type | Registers a derived type from outside the base type's assembly. |
YamlSerializableAttribute |
context | Declares a root type for source generation. |
YamlSourceGenerationOptionsAttribute |
context | Configures a generated context. |
YamlNumberHandlingAttribute applies to byte, sbyte, short, ushort, int, uint, long, ulong, nint,
nuint, float, double, decimal, and, on net11.0, BFloat16, Decimal32, Decimal64, and Decimal128, plus
their Nullable<T> counterparts. AllowNamedFloatingPointLiterals reads and writes the quoted NaN, Infinity, and
-Infinity literals for the floating-point types instead of the .nan, .inf, and -.inf scalars of the YAML core
schema; it has no effect on the integer types.
Naming policies
YamlNamingPolicyAttribute overrides YamlSerializerOptions.PropertyNamingPolicy. On a type it applies to every
member; on a member it applies to that member only. YamlPropertyNameAttribute takes precedence over both.
[YamlNamingPolicy(YamlKnownNamingPolicy.SnakeCaseLower)]
internal sealed class Product
{
public int Id { get; set; } // id
[YamlNamingPolicy(YamlKnownNamingPolicy.CamelCase)]
public string? DisplayName { get; set; } // displayName
public DateTime CreationDate { get; set; } // creation_date
}
Ignoring members
YamlIgnoreAttribute overrides YamlSerializerOptions.DefaultIgnoreCondition. On a type it sets the default condition
for every property and field; on a member it applies to that member only.
[YamlIgnore(Condition = YamlIgnoreCondition.WhenWritingNull)]
internal sealed class Product
{
public string? Name { get; set; } // omitted when null
[YamlIgnore(Condition = YamlIgnoreCondition.Never)]
public string? Description { get; set; } // always written
[YamlIgnore]
public string? Secret { get; set; } // never serialized nor deserialized
}
Ordering members
internal sealed class Product
{
[YamlPropertyOrder(1)]
public int Id { get; set; }
[YamlPropertyOrder(2)]
public string? Name { get; set; }
}
Enum member names
internal enum Level
{
Low,
[YamlEnumMemberName("very-high")]
VeryHigh, // very-high
}
Populating existing values
YamlObjectCreationHandling.Populate adds to the value a member already holds instead of replacing it. It applies to
collections and to nested objects.
internal sealed class Settings
{
[YamlObjectCreationHandling(YamlObjectCreationHandling.Populate)]
public List<string> Tags { get; set; } = ["default"];
}
var settings = YamlSerializer.Deserialize<Settings>("Tags:\n - extra\n");
Assert.Equal(["default", "extra"], settings!.Tags);
Extension data
YamlExtensionDataAttribute collects the mapping keys that match no member. The annotated member must be a
YamlMapping, or a dictionary whose keys are string and whose values are object or YamlNode:
internal sealed class Product
{
public int Id { get; set; }
[YamlExtensionData]
public IReadOnlyDictionary<string, object?>? Extra { get; set; }
}
The supported dictionary types are Dictionary<string, TValue>, IDictionary<string, TValue>, and
IReadOnlyDictionary<string, TValue>. A read-only dictionary cannot be mutated through its declared type, so the
deserializer creates a Dictionary<string, TValue>, copies the entries of the current value into it, and assigns it to
the member. The member must be settable, unless its value is already a Dictionary<string, TValue> which is then
updated in place.
Lifecycle callbacks
Implement any of these interfaces to run code around serialization:
internal sealed class Product : IYamlOnSerializing, IYamlOnSerialized, IYamlOnDeserializing, IYamlOnDeserialized
{
public void OnSerializing() { }
public void OnSerialized() { }
public void OnDeserializing() { }
public void OnDeserialized() { }
}
An exception thrown from a callback is wrapped in a YamlException.
Custom converters
Custom converters derive from YamlConverter<T> and are registered through YamlSerializerOptions.Converters,
YamlSourceGenerationOptionsAttribute.Converters, or YamlConverterAttribute.
internal sealed class UriConverter : YamlConverter<Uri>
{
public override Uri Read(YamlReader reader) => new(reader.GetScalarValue(), UriKind.RelativeOrAbsolute);
public override void Write(YamlWriter writer, Uri value) => writer.WriteString(value.ToString());
}
var options = new YamlSerializerOptions { Converters = [new UriConverter()] };
YamlReader walks the payload token by token (TokenType, Read(), Skip(), GetScalarValue(), Tag, Anchor,
Alias), and YamlWriter emits it (WriteStartMapping(), WritePropertyName(), WriteScalar() overloads for every
built-in type, WriteTag(), WriteAnchor(), WriteAlias()). The YamlScalar static class exposes the span-based
scalar parsers used by the built-in converters (TryParseInt64, TryParseDouble, IsNull, ResolveObject, …).
Override CanPopulate and Populate to support YamlObjectCreationHandling.Populate.
Derive from YamlConverterFactory to create converters dynamically for a family of types.
YamlConverterAttribute also accepts an open generic converter type when the annotated type, or the type of the
annotated member, is a generic type with the same number of generic parameters. The converter is closed over the type
arguments of the target type.
[YamlConverter(typeof(BoxConverter<>))]
private sealed class Box<T>
{
public Box(T? value) => Value = value;
public T? Value { get; }
}
private sealed class BoxConverter<T> : YamlConverter<Box<T>?> // used for Box<int>, Box<string>, ...
{
public override Box<T>? Read(YamlReader reader)
{
if (reader.TokenType is YamlTokenType.Scalar && YamlScalar.IsNull(reader.ScalarValue.AsSpan()))
{
reader.Read();
return null;
}
var scalar = reader.GetScalarValue();
reader.Read();
return new Box<T>((T)Convert.ChangeType(scalar, typeof(T), CultureInfo.InvariantCulture));
}
public override void Write(YamlWriter writer, Box<T>? value)
{
if (value is null)
{
writer.WriteNullValue();
return;
}
writer.WriteScalar(Convert.ToString(value.Value, CultureInfo.InvariantCulture));
}
}
Polymorphism
Annotate a base type with YamlPolymorphicAttribute and register its derived types with YamlDerivedTypeAttribute.
[YamlPolymorphic]
[YamlDerivedType(typeof(Dog), "dog")]
[YamlDerivedType(typeof(Cat), "cat")]
internal abstract class Animal
{
public string Name { get; set; } = "";
}
internal sealed class Dog : Animal;
internal sealed class Cat : Animal;
$type: dog
Name: Rex
| Setting | Values | Description |
|---|---|---|
DiscriminatorStyle |
Property (default), Tag, Both |
Whether the type is identified by a mapping key or by a YAML tag. |
TypeDiscriminatorPropertyName |
$type |
Key used by the Property style. |
UnknownDerivedTypeHandling |
Fail (default), FallBackToBase |
Behavior when the discriminator matches no registration. |
InferClosedTypePolymorphism |
false |
See Closed hierarchies. |
Discriminators can be strings or integers. With the Tag style, the discriminator is a YAML tag:
[YamlPolymorphic(DiscriminatorStyle = YamlTypeDiscriminatorStyle.Tag)]
[YamlDerivedType(typeof(Dog), Tag = "!dog")]
internal abstract class Animal
{
public string Name { get; set; } = "";
}
internal sealed class Dog : Animal;
!dog
Name: Rex
The same settings are available on YamlSerializerOptions.PolymorphismOptions for the whole serializer.
The discriminator is consumed while reading: the selected type does not see it, so it is neither an unmapped member nor
an extension data entry, and a selected type that is polymorphic itself does not resolve it a second time. Such a type is
read as itself unless the mapping also carries a discriminator or tag of its own: its default derived type and type
classifiers do not replace a type the discriminator selected explicitly. With the Property and Both styles, a member
or extension data entry named like the discriminator property is not written. A base type registered as one of its own
derived types is written with its discriminator.
The discriminator is only read from the mapping itself: a discriminator provided by a mapping merged into it with << is
not taken into account.
A type cannot register the same derived type, discriminator, or tag twice, nor two derived types with neither a
discriminator nor a tag. Reflection-based serialization throws an InvalidOperationException when the type is first
used, and the source generator reports the MFY026 diagnostic.
UnknownDerivedTypeHandling applies while reading. While writing, the runtime type must match a registration exactly:
serializing a subclass of a registered derived type that is not registered itself throws NotSupportedException rather
than writing it under the discriminator of its closest registered base, which would silently drop the members it
declares. This holds for both the reflection-based serializer and a source-generated YamlSerializerContext.
Registering derived types at runtime
PolymorphismOptions.DerivedTypeMappings registers derived types without touching the base type, which enables
polymorphism across assemblies (plugin systems, clean architecture). Runtime entries are merged with attribute-based
ones, and attributes win on conflicts. The mappings are copied when the YamlPolymorphismOptions instance is assigned to
YamlSerializerOptions.PolymorphismOptions, and the instance becomes read-only: changing the mappings afterwards throws
NotSupportedException, so options already in use, and the copies made from them with with, cannot change.
var options = new YamlSerializerOptions
{
PolymorphismOptions = new YamlPolymorphismOptions
{
DerivedTypeMappings =
{
[typeof(Animal)] = [new YamlDerivedType(typeof(Dog), "dog")],
},
},
};
YamlDerivedTypeMappingAttribute does the same declaratively, and is the form the source generator understands.
Open generic derived types
YamlDerivedTypeAttribute accepts an open generic derived type on a generic base type. The derived type is closed for
each instantiation of the base type by matching the base type it declares, so a single attribute covers every
instantiation.
[YamlPolymorphic]
[YamlDerivedType(typeof(Dog<>), "dog")]
internal abstract class Animal<T>
{
}
internal sealed class Dog<T> : Animal<T> // Animal<string> uses Dog<string>, Animal<int> uses Dog<int>
{
}
Closed hierarchies
A closed class hierarchy already lists its derived types in metadata, so they do not need to be repeated with
YamlDerivedTypeAttribute. Set InferClosedTypePolymorphism to register every derived type of a closed base type
automatically, using the derived type name, without the generic arity suffix, as its discriminator.
public closed class Shape
{
public string Name { get; set; } = "";
}
public sealed class Circle : Shape { public double Radius { get; set; } }
public sealed class Square : Shape { public double Side { get; set; } }
var options = new YamlSerializerOptions
{
PolymorphismOptions = new YamlPolymorphismOptions { InferClosedTypePolymorphism = true },
};
Shape shape = new Circle { Name = "circle", Radius = 3 };
var yaml = YamlSerializer.Serialize(shape, options);
Assert.Equal("$type: Circle\nName: circle\nRadius: 3.0\n", yaml);
Inference is opt-in. It can also be enabled for a single type with YamlPolymorphicAttribute, which takes precedence
over the serializer options, so InferClosedTypePolymorphism = false excludes a type from a global opt-in:
[YamlPolymorphic(InferClosedTypePolymorphism = true)]
public closed class Shape
{
}
Explicit registrations replace inference: a type declaring YamlDerivedTypeAttribute or a runtime mapping registers
only those derived types. Enabling YamlPolymorphicAttribute.InferClosedTypePolymorphism on a type that is not declared
closed throws an InvalidOperationException and reports the MFY023 diagnostic. A derived type that is itself
closed brings its own hierarchy along: its derived types are registered under the root of the hierarchy too, so the
most derived type of a fully closed hierarchy is the one written and read back. A derived type that is not closed ends
the inference, as its own derived types are not known.
A derived type must be at least as visible as the base type it is registered under, and two derived types cannot share a
name. Reflection-based serialization throws an InvalidOperationException in those cases, and the source generator skips
the derived type and reports the MFY025 diagnostic.
Merge keys
A merge key (<<) copies the entries of another mapping into the current one. Its value can be an inline mapping, an alias to an anchored mapping, or a sequence mixing both. Keys written in the mapping itself always win over merged keys, and in a merge sequence the earlier entries win over the later ones:
defaults: &defaults
timeout: 30
retries: 2
prod:
<<: *defaults
timeout: 60 # wins over the merged value
Merge keys are part of the Core and Extended schemas; they are plain keys for YamlSchemaKind.Json. An alias as the merge value requires ReferenceHandling = YamlReferenceHandling.Preserve, as aliases are only resolved in that mode. Reading the document above:
var options = new YamlSerializerOptions { ReferenceHandling = YamlReferenceHandling.Preserve };
var config = YamlSerializer.Deserialize<Dictionary<string, Dictionary<string, int>>>(yaml, options);
Assert.Equal(60, config!["prod"]["timeout"]);
Assert.Equal(2, config["prod"]["retries"]);
An alias merges the entries of the anchored mapping node as they appear in the document, the same way as an inline mapping, whatever type the anchored node was deserialized into: a member the anchored mapping leaves out is not merged. The nodes a merge alias replays count towards MaxAliasExpansionNodeCount.
C# unions
A C# union is serialized as its selected case, without a wrapper:
internal union Setting(bool, int, string);
var yaml = YamlSerializer.Serialize(new Setting(42));
Assert.Equal("42\n", yaml);
Cases are selected by YAML shape when deserializing, so a union whose cases use distinct shapes needs no configuration.
A case matching the shape of the value exactly wins: Setting reads 42 as an int and "42" as a string. When no
case matches exactly, the cases that can still read the value are used instead: a string case reads a plain 42 or
true, a char or enum case reads a number, and a case whose type has a custom converter — declared with
[YamlConverter], on the options, or on the generation options — reads any value. A nested union case matches the
shapes of its own cases, and a case referencing the union itself, such as union Node(bool, Node?), never matches. The
YamlSequence, YamlMapping, and YamlValue cases match sequences, mappings, and scalars; object, YamlNode, and
YamlElement cases match any value.
A payload matching several cases — two cases that both serialize as a mapping, for instance — fails unless a
type classifier is registered. The classifier can only select one of the matching cases. YamlUnionTypeStructuralClassifier
tells mapping cases apart by their keys:
internal union Shape(Circle, Rectangle);
internal sealed class Circle { public int Radius { get; set; } }
internal sealed class Rectangle { public int Width { get; set; } public int Height { get; set; } }
var options = new YamlSerializerOptions
{
TypeClassifiers = [new YamlUnionTypeStructuralClassifier()],
};
var shape = YamlSerializer.Deserialize<Shape>("Radius: 3", options);
Assert.Equal(3, Assert.IsType<Circle>(shape.Value).Radius);
The classifier starts with every mapping case as a candidate and eliminates the cases that do not declare a key present in the payload. Keys no case declares eliminate only the cases that reject unmapped members, and a case missing a required key is eliminated once the mapping has been read. Deserialization succeeds when exactly one candidate remains. A union declaring cases that can never be told apart is rejected when the classifier is created.
A union declaring a nullable case round-trips null to that case. When no case is nullable, null produces the default
value of the union.
YamlNumberHandlingAttribute on a union applies to its numeric cases. A quoted scalar normally selects a string case, but
with AllowReadingFromString a quoted number such as "42" also matches the numeric cases, and with
AllowNamedFloatingPointLiterals a quoted "NaN", "Infinity", or "-Infinity" also matches the floating-point
cases. A scalar matching several cases is ambiguous and needs a type classifier:
[YamlNumberHandling(YamlNumberHandling.AllowReadingFromString)]
internal union IdOrName(int, string);
Assert.Equal("alice", YamlSerializer.Deserialize<IdOrName>("\"alice\"").Value);
Assert.Equal(42, YamlSerializer.Deserialize<IdOrName>("42").Value);
Assert.Throws<YamlException>(() => YamlSerializer.Deserialize<IdOrName>("\"42\"")); // both cases match
Type classifiers
Implement YamlTypeClassifierFactory to select a union case, or a derived type, with your own rules. CanClassify
receives a context whose Kind is either YamlTypeClassifierKind.Union or YamlTypeClassifierKind.PolymorphicType:
internal sealed class ShapeClassifier : YamlTypeClassifierFactory
{
public override bool CanClassify(YamlTypeClassifierContext context)
=> context.Kind is YamlTypeClassifierKind.PolymorphicType && context.DeclaringType == typeof(Shape);
public override YamlTypeClassifier CreateYamlClassifier(YamlTypeClassifierContext context, YamlSerializerOptions options)
=> reader =>
{
// The classifier reads a copy of the value: select the derived type from the first key of the mapping
if (reader.TokenType is not YamlTokenType.StartMapping)
return null;
reader.Read();
var type = reader.GetScalarValue() switch
{
"Radius" => typeof(Circle),
"Side" => typeof(Square),
_ => null,
};
return context.DerivedTypes.Any(derivedType => derivedType.DerivedType == type) ? type : null;
};
}
The classifier reads a private copy of the value, so it may consume the reader. It never overrides an explicit
discriminator or tag: it runs only once those have failed to resolve a type, and returning null falls back to the
default derived type or the usual failure.
YamlTypeClassification.Classify and YamlTypeClassification.ClassifyBufferedNode expose the buffering logic used
internally, so a classifier can inspect a node and let the deserializer replay it afterwards.
Anchors, aliases, and merge keys
The parser, the syntax tree, and the DOM handle anchors (&name), aliases (*name), and merge keys (<<) natively.
The serializer applies extra rules.
Anchors
Declaring an anchor is always allowed when reading. Set AllowAnchors = false to reject any document that declares one.
Aliases
Resolving an alias requires object reference tracking, so ReferenceHandling must be Preserve or PreserveMinimal.
The examples of this section use this model:
internal sealed class Config
{
public Section? First { get; set; }
public Section? Second { get; set; }
}
internal sealed class Section
{
public int Timeout { get; set; }
public int Retries { get; set; }
}
var options = new YamlSerializerOptions { ReferenceHandling = YamlReferenceHandling.Preserve };
var config = YamlSerializer.Deserialize<Config>("""
First: &shared
Timeout: 30
Second: *shared
""", options);
Assert.Same(config!.First, config.Second);
Without it, an alias throws YamlException: Aliases are not supported when deserializing into '…' unless ReferenceHandling is Preserve. Set AllowAliases = false to reject aliases outright.
When writing, ReferenceHandling decides whether shared objects become anchors and aliases:
| Value | Behavior |
|---|---|
None (default) |
References are expanded. A cycle exceeds MaxDepth and throws. |
Preserve |
Every object gets an anchor; repeated references become aliases. Cycles are supported. |
PreserveMinimal |
A pre-serialization pass identifies shared and cyclic references, and only those get an anchor. |
var shared = new Section { Timeout = 1 };
var yaml = YamlSerializer.Serialize(new Config { First = shared, Second = shared },
new YamlSerializerOptions { ReferenceHandling = YamlReferenceHandling.PreserveMinimal });
First: &id001
Timeout: 1
Retries: 0
Second: *id001
With PreserveMinimal, IYamlOnSerializing and IYamlOnSerialized are still invoked once per object. Converters are
called by both passes, though: a converter with side effects can check YamlWriter.IsCollectingReferences, which is
true during the pre-serialization pass, whose output is discarded.
Merge keys
A merge key whose value is a mapping, or a sequence of mappings, is applied when reading. Later keys win over merged ones, and earlier entries of a merge sequence win over later ones.
var section = YamlSerializer.Deserialize<Section>("""
<<: { Timeout: 30, Retries: 2 }
Timeout: 60
""");
Assert.Equal(60, section!.Timeout);
Assert.Equal(2, section.Retries);
Merge keys are only recognized under the Core and Extended schemas; the Json and Failsafe schemas treat << as
an ordinary key. A merge key whose value is an alias additionally requires ReferenceHandling to be Preserve or
PreserveMinimal.
Schemas
YamlSchemaKind selects the rules used to resolve untagged scalars:
| Kind | Description |
|---|---|
Failsafe |
YAML 1.2 §10.1. Every plain scalar is a string. |
Json |
YAML 1.2 §10.2. JSON-compatible resolution: a plain scalar must be null, true, false, or a JSON number. |
Core |
YAML 1.2 §10.3. The default; adds ~, Null, TRUE, hexadecimal and octal integers, .inf, .nan. |
Extended |
Adds y/yes/on booleans, !!timestamp, !!merge, and _ digit separators. |
With Schema = Json, the writer double-quotes every scalar that is not null, a boolean, or a number (strings, keys, enum
names, dates, identifiers, ...), and writes the non-finite floating-point values with an explicit !!float tag. With
UseSchema = true as well, reading rejects an untagged plain scalar that is not null, true, false, or a JSON number,
as the JSON schema does, so name: text throws while "name": "text" is accepted.
A binary floating-point value (double, float, Half, BFloat16) is always written so it resolves to a float: a
whole number keeps a fraction (1.0), negative zero is -0.0, and the special values are .inf, -.inf, and .nan.
decimal and the IEEE 754 decimal types keep the exact text of their value, whose scale is significant.
Scalar resolution goes through the selected schema only when UseSchema = true. Otherwise built-in converters use a
faster, permissive span-based path, which also accepts binary integers and digit separators while honoring quoted
scalars as strings. Null, booleans, .inf and .nan keep the core schema spellings in both paths, and an explicit
!!null, !!bool, !!int or !!float tag is honored when reading into object; a scalar that does not match its tag
throws a YamlException. Set UseSchema = true to apply the selected schema’s scalar spelling rules.
var options = new YamlSerializerOptions { UseSchema = true, Schema = YamlSchemaKind.Extended };
var values = YamlSerializer.Deserialize<Dictionary<string, object>>("enabled: yes", options);
Assert.True(values!["enabled"] is true); // true, not "yes"
The schema types (FailsafeSchema, JsonSchema, CoreSchema, ExtendedSchema) are public, derive from SchemaBase,
and implement IYamlSchema. They expose tag expansion and shortening, scalar rules, and tag registration, so a custom
schema can be built by deriving from one of them and overriding PrepareScalarRules.
Source generation
Declare a partial context derived from YamlSerializerContext and annotate each root type with
YamlSerializableAttribute. Both are in the Meziantou.Framework.Yaml.Serialization namespace. This context serializes
the Product type of Serialize and deserialize objects:
[YamlSerializable(typeof(Product))]
[YamlSourceGenerationOptions(
PropertyNamingPolicy = YamlKnownNamingPolicy.KebabCaseLower,
WriteIndented = true)]
public sealed partial class AppYamlContext : YamlSerializerContext
{
}
var product = new Product { Id = 1, DisplayName = "Sample product", Tags = ["new"] };
var yaml = YamlSerializer.Serialize(product, AppYamlContext.Default.Product);
Assert.Equal("id: 1\nname: Sample product\ntags:\n - new\n", yaml);
var copy = YamlSerializer.Deserialize(yaml, AppYamlContext.Default.Product);
Assert.Equal("Sample product", copy!.DisplayName);
The context itself can be passed to the serializer:
var product = new Product { Id = 1, DisplayName = "Sample product" };
var yaml = YamlSerializer.Serialize(product, AppYamlContext.Default);
var copy = YamlSerializer.Deserialize<Product>(yaml, AppYamlContext.Default);
Assert.Equal("Sample product", copy!.DisplayName);
YamlSourceGenerationOptionsAttribute mirrors most of YamlSerializerOptions, including Converters. A context can
also be constructed with a YamlSerializerOptions instance, and CreateOptions derives an options instance that keeps
the context as its resolver:
var options = AppYamlContext.Default.CreateOptions(o => o with { SourceName = "config.yaml" });
The attribute values initialize the options of AppYamlContext.Default, and the generated code reads most options at run
time, so an options instance with other values is honored. A few options shape the generated code instead, and an options
instance cannot change them:
PropertyNamingPolicyis always applied at build time: the member names are generated from the policy set on the attribute, and the policy of an options instance is ignored.DictionaryKeyPolicyis read at run time.DefaultIgnoreCondition,IncludeFields,IgnoreReadOnlyFields,IgnoreReadOnlyProperties,UnmappedMemberHandling,PreferredObjectCreationHandling,DuplicateKeyHandling,MappingOrder,PropertyNameCaseInsensitive,DiscriminatorStyle,TypeDiscriminatorPropertyName,UnknownDerivedTypeHandling,InferClosedTypePolymorphism, and whetherSchemaapplies merge keys are applied at build time when the attribute sets them, and read from the options at run time otherwise. A closed hierarchy is always known at build time, so its derived types are generated even when only the options can enable inference.
YamlSerializableAttribute.TypeInfoPropertyName renames the generated property when the default name collides.
Non-public members annotated with YamlIncludeAttribute and non-public constructors annotated with
YamlConstructorAttribute are supported: the generated context reaches them through UnsafeAccessorAttribute, which
NativeAOT and trimming understand.
Diagnostics
| Id | Severity | Description |
|---|---|---|
MFY001 |
Error | The context type must be declared partial. |
MFY002 |
Error | A member uses a type the generator cannot serialize. |
MFY003 |
Error | An extension data member uses an unsupported type. |
MFY004 |
Error | A type declares several [YamlExtensionData] members. |
MFY005 |
Error | A [YamlSourceGenerationOptions] value is invalid. |
MFY006 |
Error | A converter type is invalid. |
MFY007 |
Error | A serializable type, or the element or key type of a serializable collection, is not supported. |
MFY020 |
Error | [YamlDerivedTypeMapping] declares a type that is not assignable to the base type. |
MFY021 |
Warning | The base type of a [YamlDerivedTypeMapping] has no [YamlPolymorphic]; serializer defaults are used. |
MFY022 |
Warning | An open generic derived type cannot be closed for the base type and is ignored. |
MFY023 |
Error | InferClosedTypePolymorphism is enabled on a type that is not closed. |
MFY024 |
Warning | Inference is replaced by the explicit [YamlDerivedType] registrations of the type. |
MFY025 |
Warning | An inferred derived type is ignored (visibility, or duplicate discriminator). |
MFY026 |
Error | A type registers the same derived type, discriminator, or tag twice, or two default derived types. |
NativeAOT and trimming
The package is annotated IsAotCompatible and IsTrimmable. Source generation avoids reflection-based metadata
discovery and is the preferred mode for NativeAOT and trimming-sensitive applications.
Reflection-based serialization can be disabled entirely for applications that only use source-generated metadata, via
the MeziantouFrameworkYamlIsReflectionEnabledByDefault MSBuild property:
<PropertyGroup>
<MeziantouFrameworkYamlIsReflectionEnabledByDefault>false</MeziantouFrameworkYamlIsReflectionEnabledByDefault>
</PropertyGroup>
The property is published as a runtime host configuration option and as a trimmer feature switch, so the reflection code
paths are removed from the trimmed output. It defaults to false when PublishAot or NativeAot is true; set it
explicitly to override that. YamlSerializer.IsReflectionEnabledByDefault reports the effective value at runtime.
When reflection is disabled, use source-generated YamlSerializerContext metadata for typed serialization and
deserialization.
Document Object Model
Use the DOM when you need to inspect or transform YAML without binding to a CLR type. Its types are in the
Meziantou.Framework.Yaml.Model namespace.
var stream = YamlStream.Load(new StringReader("""
product:
id: 1
name: Sample product
"""));
var document = stream[0];
var root = (YamlMapping)document.Contents!;
var product = (YamlMapping)root["product"]!;
var name = ((YamlValue)product["name"]!).Value;
Assert.Equal("Sample product", name);
YamlStream is a list of YamlDocument, so a multi-document stream is read in one pass. YamlMapping implements both
IDictionary<YamlElement, YamlElement> and IList<KeyValuePair<…>>, so entries keep their document order and can be
inserted at a given index. YamlMapping and YamlSequence expose Style (Block or Flow), and YamlElement
exposes Anchor and Tag.
Every node supports DeepClone(), WriteTo(TextWriter), EnumerateEvents(), and bridges to the serializer:
var product = new Product { Id = 1, DisplayName = "Sample product" };
var node = YamlNode.FromObject(product);
var back = node.ToObject<Product>();
Assert.Equal("Sample product", back!.DisplayName);
A YamlNode can also be used directly as a member type, which is how [YamlExtensionData] keeps unmatched content in
its original form.
Syntax tree
YamlSyntaxTree produces a lossless representation: with IncludeTrivia, comments and whitespace are preserved and
ToFullString() returns the original text byte for byte. Every node and token carries a Span and a FullSpan. The
syntax tree types are in the Meziantou.Framework.Yaml.Syntax namespace.
var yaml = """
# deployment settings
replicas: 3 # keep in sync with the chart
""";
var tree = YamlSyntaxTree.Parse(yaml, new YamlSyntaxOptions { IncludeTrivia = true });
var tokens = new List<string>();
foreach (var token in tree.Tokens)
{
tokens.Add($"{token.Kind} {token.Span} {token.Text}");
}
Assert.Equal(
[
"StreamStart 0:0 ",
"CommentTrivia 0:21 # deployment settings",
"NewLineTrivia 21:22 \n",
"BlockMappingStart 22:22 ",
"Key 22:22 ",
"Scalar 22:30 replicas",
"Value 30:31 :",
"WhitespaceTrivia 31:32 ",
"Scalar 32:33 3",
"WhitespaceTrivia 33:34 ",
"CommentTrivia 34:63 # keep in sync with the chart",
"BlockEnd 63:63 ",
"StreamEnd 63:63 ",
], tokens);
Assert.Equal(yaml, tree.ToFullString());
This is the layer to use for tooling: linters, formatters, or edits that must not disturb the rest of the file.
Parser and emitter
The event-based API mirrors libyaml and is available for streaming scenarios. Parser and EventReader are in the
Meziantou.Framework.Yaml namespace, and the events in Meziantou.Framework.Yaml.Events. This example lists the keys of
the root mapping of each document:
var yaml = """
name: first
items: [1, 2]
---
name: second
""";
var parser = Parser.CreateParser(new StringReader(yaml));
var reader = new EventReader(parser);
var keys = new List<string>();
reader.Expect<StreamStart>();
while (!reader.Accept<StreamEnd>())
{
reader.Expect<DocumentStart>();
reader.Expect<MappingStart>();
while (reader.Allow<MappingEnd>() is null)
{
keys.Add(reader.Expect<Scalar>().Value);
reader.Skip(); // Skip the value, and its children for a collection
}
reader.Expect<DocumentEnd>();
}
Assert.Equal(["name", "items", "name"], keys);
EventReader also provides Peek<T>(), which reads the next event without consuming it, and Skip(untilDepth).
Emitter writes parsing events to a TextWriter and accepts an indentation width, a maximum line width, and a
canonical mode.
Error handling
Every parse and binding failure derives from YamlException, which carries the Start and End marks (line, column,
character index) of the offending node. SyntaxErrorException and SemanticErrorException cover scanning and parsing
failures.
Set SourceName to prefix messages with the origin of the document:
var options = new YamlSerializerOptions { SourceName = "config.yaml" };
var exception = Assert.Throws<YamlException>(() => YamlSerializer.Deserialize<Product>("Id: [1, 2]", options));
Assert.Equal("config.yaml: (Lin: 0, Col: 4, Chr: 4) - (Lin: 0, Col: 4, Chr: 4): Expected a Scalar token but found 'StartSequence'.", exception.Message);
Use TryDeserialize when a failure is expected and an exception is not wanted.
Hardening untrusted input
| Option | Default | Purpose |
|---|---|---|
MaxDepth |
64 |
Caps the nesting depth of mappings and sequences while reading and writing. 0 means the default. |
AllowAnchors |
true |
Rejects documents declaring anchors. |
AllowAliases |
true |
Rejects documents using aliases, which prevents alias-expansion amplification. |
MaxAliasExpansionNodeCount |
100000 |
Caps how many nodes aliases may materialize when building a YamlNode model. 0 means the default. |
DuplicateKeyHandling |
Error |
Rejects mappings with duplicate keys. |
UnsafeAllowDeserializeFromTagTypeName |
false |
Allows a YAML tag to name a CLR type to instantiate. Enable it only for trusted input. |
var options = new YamlSerializerOptions
{
MaxDepth = 32,
AllowAnchors = false,
AllowAliases = false,
UnmappedMemberHandling = YamlUnmappedMemberHandling.Disallow,
};
Assert.Throws<YamlException>(() => YamlSerializer.Deserialize<Product>("Id: &id 1", options));
Assert.Throws<YamlException>(() => YamlSerializer.Deserialize<Product>("Price: 10", options));
The document object model expands each alias into a copy of the anchored subtree, so nesting anchors makes the node
count grow exponentially while the document grows linearly. MaxDepth does not bound this, because the growth is in
breadth rather than depth. MaxAliasExpansionNodeCount bounds it directly, and only counts nodes produced by alias
expansion, so documents that do not use aliases are never affected.
Pass the options to YamlStream.Load when reading untrusted input into the model; the parameterless overload uses
YamlSerializerOptions.Default:
var options = new YamlSerializerOptions { MaxAliasExpansionNodeCount = 1_000 };
using var reader = new StringReader("""
base: &base { name: default }
copy: *base
""");
var stream = YamlStream.Load(reader, options);
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
-
net10.0
- No dependencies.
-
net11.0
- No dependencies.
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Meziantou.Framework.Yaml:
| Package | Downloads |
|---|---|
|
Meziantou.Framework.DependencyScanning
Find dependencies in source files. Support multiple package managers such as NuGet, npm, Docker, PyPi, and so on |
|
|
Meziantou.Framework.Mermaid
Reads and writes the Mermaid text format: the 25 diagram types of Meziantou.Framework.Diagrams, and the pie, XY, quadrant, sankey and radar charts and treemaps of Mermaid drawn with Meziantou.Framework.Charts, with the front matter, the directives and the themes of Mermaid. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.12 | 71 | 11/13/2026 |
| 2.0.11 | 168 | 11/8/2026 |
| 2.0.10 | 416 | 10/28/2026 |
| 2.0.9 | 243 | 10/24/2026 |
| 2.0.8 | 144 | 10/21/2026 |
| 2.0.7 | 210 | 10/16/2026 |
| 2.0.6 | 219 | 10/11/2026 |
| 2.0.5 | 302 | 9/27/2026 |
| 2.0.4 | 316 | 9/20/2026 |
| 2.0.3 | 174 | 9/15/2026 |
| 2.0.2 | 132 | 9/13/2026 |
| 2.0.1 | 164 | 9/11/2026 |
| 2.0.0 | 190 | 9/8/2026 |
| 1.0.10 | 140 | 9/7/2026 |
| 1.0.9 | 179 | 9/6/2026 |
| 1.0.8 | 298 | 9/3/2026 |
| 1.0.7 | 134 | 9/3/2026 |
| 1.0.6 | 125 | 9/3/2026 |
| 1.0.5 | 341 | 8/23/2026 |
| 1.0.4 | 286 | 8/16/2026 |