Skip to content

System.Text.Json Polymorphism Type Discriminator Does Not Support Subtypes Class Polymorphism #101286

Closed

Description

@bunnyi116

Description

System.Text.Json Polymorphism Type Discriminator Does Not Support Subtypes Class Polymorphism

System.Text.Json Version

9.0.0-preview.3.24172.9

Test

var jsonText = """
    {"time":1713524900,"self_id":123456789,"post_type":"meta_event","meta_event_type":"lifecycle","sub_type":"connect"}
    """;


var eventBase = JsonSerializer.Deserialize<EventBase>(jsonText, options: new()
{
     AllowOutOfOrderMetadataProperties = true
});
Console.WriteLine(eventBase);
[JsonPolymorphic(TypeDiscriminatorPropertyName = "post_type")]
[JsonDerivedType(typeof(MetaEvent), "meta_event")]
// ......
public record EventBase
{
    [JsonPropertyName("time")]
    public long Time { get; init; }

    [JsonPropertyName("self_id")]
    public long SelfId { get; init; }

    [JsonPropertyName("post_type")]
    public virtual string PostType { get; init; } = "unknown";
}

[JsonPolymorphic(TypeDiscriminatorPropertyName = "meta_event_type")]
[JsonDerivedType(typeof(MetaLifecycleEvent), "lifecycle")]
// ......
public record MetaEvent : EventBase
{
    [JsonPropertyName("post_type")]
    public override string PostType { get; init; } = "meta_event";

    [JsonPropertyName("meta_event_type")]
    public virtual string MetaEventType { get; init; } = "unknown";
}

public record MetaLifecycleEvent : MetaEvent
{
    [JsonPropertyName("meta_event_type")]
    public override string MetaEventType { get; init; } = "lifecycle";

    [JsonPropertyName("sub_type")]
    public required string SubType { get; init; }
}

image

Activity

  1. eiriktsarpalis commented on Apr 19, 2024

    @eiriktsarpalis
    Member

    I tried running the reproduction and it seems to be working as expected, i.e. the discriminator "post_type" : "meta_event" is being mapped to an instance of type MetaEvent, when deserializing as EventBase. I'm not entirely sure what your expected behavior is in this case, but I have two remarks based on what I'm seeing:

    1. The JsonDerivedType/JsonPolymorphic annotations on MetaEvent are not inherited by EventBase. These set up independent sets of configurations depending on whether you are calling Deserialize<EventBase> or Deserialize<MetaEvent>.
    2. I see you are creating properties whose names match the type discriminator property name. I should note that this isn't supported, the run-time value of these properties (as set via the overrides) does not have any influence on what a payload deserializes to. This is solely guided by the attribute annotations.

    Hope this helps.

  2. added
    needs-author-actionAn issue or pull request that requires more info or actions from the author.
    on Apr 19, 2024
  3. bunnyi116 commented on Apr 19, 2024

    @bunnyi116
    Author

    How should I deserialize into the MetaLifecycleEvent class?

    EventBase is the base class for all events, MetaEvent is a broad category of events, with MetaLifecycleEvent belonging to the MetaEvent class, which in turn belongs to EventBase.

  4. bunnyi116 commented on Apr 19, 2024

    @bunnyi116
    Author

    I think that when serializing/deserializing, if a subclass specified, it should check whether the subclass has the JsonPolymorphic or JsonDerivedType attributes. If these attributes exist, the process should continue.

  5. eiriktsarpalis commented on Apr 19, 2024

    @eiriktsarpalis
    Member

    You would need to add a JsonDerivedType annotation for MetaLifecycleEvent in EventBase. If you're feeling fancy (and you're not working with Native AOT or trimmed applications) you can write a contract customizer that looks up the type hierarchy using reflection and automatically appends JsonDerivedAttribute annotations to the base.

    I think that when serializing/deserializing, if a subclass specified, it should check whether the subclass has the JsonPolymorphic or JsonDerivedType attributes. If these attributes exist, the process should continue.

    That might work when using reflection-based serialization, but it might not be possible using the source generator which needs to know the entire hierarchy at compile time. Therefore, it is not supported (though there are ways to achieve this via contract customization).

  6. bunnyi116 commented on Apr 19, 2024

    @bunnyi116
    Author

    I don't need AOT.

    Could you help me write a simple example using custom contracts for my sample data? I'm not very familiar with it.

    Also, I hope STJ can find a way to support AOT in the future.

  7. locked and limited conversation to collaborators on Apr 19, 2024
  8. converted this issue into a discussion #101289 on Apr 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions