SharpYaml supports polymorphism in a JSON-like way by default: using a discriminator property within a mapping.
It also supports YAML tags as an alternative discriminator style.
If the base type and its derived types live in different projects, the base type often cannot reference every concrete implementation. SharpYaml supports that scenario in both serialization modes:
YamlPolymorphismOptions.DerivedTypeMappingsYamlDerivedTypeMappingAttributeBoth approaches are additive to YamlDerivedTypeAttribute and JsonDerivedTypeAttribute. Attribute-based registrations on the base type take precedence when the same discriminator or derived type is declared more than once.
using SharpYaml;
var options = new YamlSerializerOptions
{
PolymorphismOptions = new YamlPolymorphismOptions
{
DerivedTypeMappings =
{
[typeof(Animal)] = new List<YamlDerivedType>
{
new(typeof(Dog), "dog") { Tag = "!dog" },
new(typeof(Cat), "cat") { Tag = "!cat" },
}
}
}
};
using SharpYaml.Serialization;
[YamlSerializable(typeof(Zoo))]
[YamlDerivedTypeMapping(typeof(Animal), typeof(Dog), "dog", Tag = "!dog")]
[YamlDerivedTypeMapping(typeof(Animal), typeof(Cat), "cat", Tag = "!cat")]
internal partial class ZooYamlContext : YamlSerializerContext
{
}
Types referenced by YamlDerivedTypeMappingAttribute are automatically included in the generated context, so you do not need a separate [YamlSerializable] entry for each mapped derived type unless you want an explicit YamlTypeInfo<T> property name.
Use JsonPolymorphicAttribute and JsonDerivedTypeAttribute:
using System.Text.Json.Serialization;
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(Dog), "dog")]
[JsonDerivedType(typeof(Cat), "cat")]
public abstract class Animal
{
public string Name { get; set; } = "";
}
When serialized, a discriminator property is emitted:
$type: dog
Name: Rex
Use YamlPolymorphicAttribute and YamlDerivedTypeAttribute:
using SharpYaml.Serialization;
[YamlPolymorphic(DiscriminatorStyle = YamlTypeDiscriminatorStyle.Tag)]
[YamlDerivedType(typeof(Dog), "dog", Tag = "!dog")]
public abstract class TaggedAnimal
{
}
A derived type registered without a discriminator acts as the default when the discriminator property is missing or unrecognized. This works with both JsonDerivedTypeAttribute and YamlDerivedTypeAttribute:
[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(Cat), "cat")]
[JsonDerivedType(typeof(OtherAnimal))] // default - no discriminator
public abstract class Animal
{
public string Name { get; set; } = "";
}
# Deserialized as Cat (discriminator matches)
type: cat
Name: Biscuit
# Deserialized as OtherAnimal (no discriminator → default)
Name: Cupcake
The equivalent using YAML attributes:
[YamlPolymorphic]
[YamlDerivedType(typeof(Dog), "dog")]
[YamlDerivedType(typeof(OtherAnimal))] // default - no discriminator
public abstract class Animal { }
When serializing a default derived type, no discriminator property is emitted.
Discriminator values can be integers instead of strings. Both JsonDerivedTypeAttribute and YamlDerivedTypeAttribute accept an int argument:
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(Dog), 1)]
[JsonDerivedType(typeof(Cat), 2)]
public abstract class Animal
{
public string Name { get; set; } = "";
}
[YamlPolymorphic]
[YamlDerivedType(typeof(Dog), 1)]
[YamlDerivedType(typeof(Cat), 2)]
public abstract class Animal
{
public string Name { get; set; } = "";
}
The integer is emitted as a plain YAML scalar:
$type: 1
Name: Rex
Integer discriminators are stored internally as strings (using invariant culture) so they work identically in both reflection mode and source-generated mode.
By default, an unrecognized discriminator value causes deserialization to throw. You can override this per-type via YamlPolymorphicAttribute or globally via YamlPolymorphismOptions:
// Per-type: fall back to the base type on unknown discriminators
[YamlPolymorphic(UnknownDerivedTypeHandling = YamlUnknownDerivedTypeHandling.FallBackToBase)]
[YamlDerivedType(typeof(Circle), "circle")]
public class Shape { }
The YamlPolymorphicAttribute.UnknownDerivedTypeHandling property takes precedence over the corresponding JsonPolymorphicAttribute setting and the global YamlPolymorphismOptions.UnknownDerivedTypeHandling.
YamlDerivedTypeAttribute / JsonDerivedTypeAttribute) over free-form type names.