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.

Cross-project registration

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:

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

Reflection mode

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" },
            }
        }
    }
};

Source-generated mode

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.

JSON-like discriminator property

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

YAML tag discriminator

Use YamlPolymorphicAttribute and YamlDerivedTypeAttribute:

using SharpYaml.Serialization;

[YamlPolymorphic(DiscriminatorStyle = YamlTypeDiscriminatorStyle.Tag)]
[YamlDerivedType(typeof(Dog), "dog", Tag = "!dog")]
public abstract class TaggedAnimal
{
}

Default derived type

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.

Integer discriminators

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.

Unknown discriminator handling

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.

Notes