skip to Main Content

Here are my codes:

public partial class MainWindow : Window
{        
    public MainWindow()
    {
        InitializeComponent();
        Debug.WriteLine(JsonSerializer.Serialize<TestModel>(new TestModel() { Name = "123", Time = DateTime.Now }));
        Debug.WriteLine(JsonSerializer.Serialize<TestModelWithGeneric<int>>(new TestModelWithGeneric<int>() { Name = "123", Time = DateTime.Now ,Value=1}));
        Debug.WriteLine(JsonSerializer.Serialize<TestModelWithGeneric<string>>(new TestModelWithGeneric<string>() { Name = "123", Time = DateTime.Now, Value = "123" }));
    }
    [JsonDerivedType(typeof(TestModel), typeDiscriminator: "Base")]
    [JsonDerivedType(typeof(TestModelWithGeneric<int>), typeDiscriminator: "TestModelWithInt")]
    [JsonDerivedType(typeof(TestModelWithGeneric<string>), typeDiscriminator: "TestModelWithString")]
    public class TestModel {
        public string Name{ get; set; }
        public DateTime Time { get; set; }
    }
    public class TestModelWithGeneric<T> :TestModel{ 
        public T Value { get; set; }
    }
}   

The output is:

{"$type":"Base","Name":"123","Time":"2023-12-04T15:29:49.7867248+08:00"}
{"Value":1,"Name":"123","Time":"2023-12-04T15:29:49.8314012+08:00"}
{"Value":"123","Name":"123","Time":"2023-12-04T15:29:49.8354174+08:00"}

As you see, when there is a generic type, it always missing $type that JsonDerivedType no working.

What’s wrong with my code?

The framework I am working is .Net8 .

2

Answers


  1. Chosen as BEST ANSWER

    Problem solved! According to the How to serialize properties of derived classes with System.Text.Json doc:

    For polymorphic serialization to work, the type of the serialized value should be that of the polymorphic base type. This includes using the base type as the generic type parameter when serializing root-level values, as the declared type of serialized properties, or as the collection element in serialized collections.

    I should call JsonSerializer.Serialize<T> with explicit TestModel provided for the generic argument:

     Debug.WriteLine(JsonSerializer.Serialize<TestModel>(new TestModel() { Name = "123", Time = DateTime.Now }));
     Debug.WriteLine(JsonSerializer.Serialize<TestModel>(new TestModelWithGeneric<int>() { Name = "123", Time = DateTime.Now ,Value=1}));
     Debug.WriteLine(JsonSerializer.Serialize<TestModel>(new TestModelWithGeneric<string>() { Name = "123", Time = DateTime.Now, 
         Value = "123" }));
    

    Then it works now!


  2. TestModelWithGeneric<T> does not have any polymorphic serialization info added so the System.Text.Json serializer will not apply it when the serialized generic type is resolved as TestModelWithGeneric<T> (generic types are resolved at compile time). You can specify type explicitly:

    Console.WriteLine(JsonSerializer.Serialize<TestModel>(new TestModel() { Name = "123", Time = DateTime.Now }));
    Console.WriteLine(JsonSerializer.Serialize<TestModel>(new TestModelWithGeneric<int>() { Name = "123", Time = DateTime.Now ,Value=1}));
    Console.WriteLine(JsonSerializer.Serialize<TestModel>(new TestModelWithGeneric<string>() { Name = "123", Time = DateTime.Now, 
         Value = "123" }));
    

    But this is not ideal option because it is easy to miss calls (for example the call JsonSerializer.Serialize(new TestModelWithGeneric<string>() ...) will infer the "incorrect" type of TestModelWithGeneric<string>). Another option would be to use custom IJsonTypeInfoResolver. For example simple one can look like (handles only TestModel but can be expanded for more generic handling):

    public class JsonHierarchyTypeInfoResolver : DefaultJsonTypeInfoResolver
    {
        public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
        {
            var result = base.GetTypeInfo(type, options);
            if (type != typeof(TestModel) && type.IsAssignableTo(typeof(TestModel)))
            {
                var baseInfo = base.GetTypeInfo(typeof(TestModel), options);
                foreach (var derivedType in baseInfo.PolymorphismOptions.DerivedTypes)
                {
                    if (derivedType.DerivedType.IsAssignableTo(type))
                    {
                        result.PolymorphismOptions ??= new JsonPolymorphismOptions();
                        result.PolymorphismOptions.DerivedTypes.Add(derivedType);
                    }
                }
            }
    
            return result;
        }
    }
    

    And usage:

    var options = new JsonSerializerOptions
    {
        TypeInfoResolver = new JsonHierarchyTypeInfoResolver()
    };
    Console.WriteLine(JsonSerializer.Serialize(
        new TestModel { Name = "123", Time = DateTime.Now }
        , options));
    Console.WriteLine(JsonSerializer.Serialize(
        new TestModelWithGeneric<int> { Name = "123", Time = DateTime.Now ,Value=1}
        , options));
    Console.WriteLine(JsonSerializer.Serialize(
        new TestModelWithGeneric<string> { Name = "123", Time = DateTime.Now, Value = "123" }
        , options));
    

    Which results in:

    {"$type":"Base","Name":"123","Time":"2023-12-04T12:37:43.9072465+03:00"}
    {"$type":"TestModelWithInt","Value":1,"Name":"123","Time":"2023-12-04T12:37:44.1239771+03:00"}       
    {"$type":"TestModelWithString","Value":"123","Name":"123","Time":"2023-12-04T12:37:44.1282147+03:00"}
    

    See also:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search