Configure polymorphic JSON serialization using [JsonPolymorphic] and [JsonDerivedType] attributes for type-safe inheritance hierarchies.
When to Use
- Serializing inheritance hierarchies with System.Text.Json
- Adding type discriminators for polymorphic deserialization
- Preparing polymorphic types for AOT-compatible JSON source generation
- Replacing Newtonsoft.Json
TypeNameHandling with [JsonPolymorphic]/[JsonDerivedType]
Requirements
Steps
- Ask scope:
- Ask user: "Apply to entire solution or specific project?" - If specific project, ask which project
- Search for existing
[JsonPolymorphic] attributes:
- Detect existing project conventions - Note any TypeDiscriminatorPropertyName values found
- Extract discriminator conventions:
- Search for TypeDiscriminatorPropertyName in existing attributes - Record all unique discriminator property names found
- Find polymorphic candidates:
- Search for abstract classes with derived types - Search for interfaces with implementing classes used in serialization - Look for classes with virtual members that are inherited
- Ask about discriminator property name:
- If existing discriminators found, show them to user - Ask: "What discriminator property name? (Default: $type, Found: [list existing])"
- Check for consistency:
- If multiple different discriminators found in codebase - Ask: "Found multiple discriminators: [list]. Standardize to single value?"
- Apply attributes to base types:
``csharp [JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")] [JsonDerivedType(typeof(DerivedA), typeDiscriminator: "DerivedA")] [JsonDerivedType(typeof(DerivedB), typeDiscriminator: "DerivedB")] public abstract class BaseClass { } ` - Use full type name as discriminator value (e.g., "WeatherForecastWithCity"`)
- Add using directive:
- Ensure using System.Text.Json.Serialization; is present
- Verify with build:
``bash dotnet build ``
- Report results:
- List all base types configured - List all derived type mappings added - Confirm build status
Example Conversion
Before:
public abstract class Notification
{
public string Message { get; set; }
}
public class EmailNotification : Notification
{
public string EmailAddress { get; set; }
}
public class SmsNotification : Notification
{
public string PhoneNumber { get; set; }
}
After:
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(EmailNotification), typeDiscriminator: "EmailNotification")]
[JsonDerivedType(typeof(SmsNotification), typeDiscriminator: "SmsNotification")]
public abstract class Notification
{
public string Message { get; set; }
}
public class EmailNotification : Notification
{
public string EmailAddress { get; set; }
}
public class SmsNotification : Notification
{
public string PhoneNumber { get; set; }
}
JSON Output:
{
"$type": "EmailNotification",
"message": "Hello",
"emailAddress": "[email protected]"
}
Notes
- AOT Compatibility: Metadata-based source generation is supported; fast-path source generation is NOT supported for polymorphic types
- Serialization requirement: Must serialize using the base type for polymorphism to work (e.g.,
JsonSerializer.Serialize<Notification>(emailNotification))
- Discriminator values: String discriminators are recommended over integers for readability and forward compatibility
- Nested hierarchies: Each level in the hierarchy needs its own
[JsonPolymorphic] attribute if it has derived types
- Interface support: Interfaces can also use
[JsonPolymorphic] when they define the contract for serialization
Documentation