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.
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.