Tomlyn supports polymorphism using a discriminator key (defaulting to $type) to determine which derived type to instantiate. You can implement this using three different methods depending on your architecture and runtime requirements:
1. Attribute-based (Standard)
Use [TomlPolymorphic] and [TomlDerivedType] on your base class. This is the simplest method for most applications.
2. Cross-project runtime mappings (Reflection)
If the base type and derived types are in different projects (e.g., a plugin architecture), use TomlPolymorphismOptions.DerivedTypeMappings to register types at runtime using the reflection resolver.
3. Cross-project source generation (NativeAOT/Trimming)
For NativeAOT or trimming-safe code, use [TomlDerivedTypeMapping] on a TomlSerializerContext to register mappings during source generation.
Key Features:
- Custom Discriminators: Change the key name via
TomlPolymorphicAttribute.TypeDiscriminatorPropertyName or TomlPolymorphismOptions.TypeDiscriminatorPropertyName. - Default Types: Register a derived type without a discriminator to act as the default. If the TOML lacks a discriminator or has an unknown one, it will deserialize as this type. When serializing the default type, no discriminator is emitted.
- Integer Discriminators:
[TomlDerivedType] accepts an int which is stored as a string in the TOML file. - JSON Compatibility:
JsonPolymorphicAttribute and JsonDerivedTypeAttribute are supported. If both Toml and Json attributes are present, Toml attributes take precedence.
// Attribute-based example
[TomlPolymorphic]
[TomlDerivedType(typeof(Cat), "cat")]
[TomlDerivedType(typeof(Dog), "dog")]
public abstract class Animal
{
public string Name { get; set; } = "";
}
public sealed class Cat : Animal { public bool Indoor { get; set; } }
public sealed class Dog : Animal { public string Breed { get; set; } = ""; }