Why System.Text.Json Fails to Deserialize Json to Generic Class

208 Views Asked by At

I have two classes and enum:

public abstract class Changed
{
    public abstract object OldValue { get; set; }
    public abstract object NewValue { get; set; }
    public TrackedPropertyType Type { get; set; }
}
    
public class Changed<T> : Changed
{
    public override object OldValue { get; set; }
    public override object NewValue { get; set; }
    
    public Changed(T oldValue, T newValue, TrackedPropertyType type)
    {
        OldValue = oldValue;
        NewValue = newValue;
        Type = type;
    }
}

public enum TrackedPropertyType
{
    PhoneNumber,
    Name,
    Email,
    BirthDate,
    WorkedInDodo,
    SocialNetworkMessagingEnable,
    IsKeyWord,
    IsBanned,
    IsFraud,
    IsRemove,
}

I can serialize instance of this class to json without any problem. For example:

{"type": 6, "newValue": 1, "oldValue": 2}

or

{"type": 5, "newValue": true, "oldValue": true}

but when I try deserialize it like this:

JsonSerializer.Deserialize<Changed<string>>(json, options);

I'm getting:

System.InvalidOperationException: Each parameter in the deserialization constructor on type 'Dodo.Control.Ratings.Model.MysteryShoppers.Events.Changed`1[System.Boolean]' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. Fields are only considered when 'JsonSerializerOptions.IncludeFields' is enabled. The match can be case-insensitive.

Options contain:

  • PropertyNameCaseInsensitive: true
  • JsonCamelCaseNamingPolicy: JsonCamelCaseNamingPolicy
1

There are 1 best solutions below

4
On BEST ANSWER

This will work for you. Edit, I upgraded the example to serialize and deserialize a complex object.

class Program
{
    public static void Main(String[] args)
    {
        Changed<TestClass> changedTestClass = new Changed<TestClass>();

        changedTestClass.NewValue = new TestClass() { 
            Name = "Test class 2",
            Id = 2,
            PropertyType = TrackedPropertyType.Email
        };

        changedTestClass.OldValue = new TestClass() { 
            Name = "Test class 1",
            Id = 1,
            PropertyType = TrackedPropertyType.PhoneNumber
        };

        var stringResult = JsonSerializer.Serialize(changedTestClass);
        var result = JsonSerializer.Deserialize<Changed<TestClass>>(stringResult);
       
    }
}

public class Changed<T> : IValues<T>
{
    public T OldValue { get; set; }
    public T NewValue { get; set; }
    public TrackedPropertyType PropertyType { get; set; }
}

public class TestClass
{
    public string Name { get; set; }
    public int Id { get; set; }

    public TrackedPropertyType PropertyType { get; set; }
}

public interface IValues<T> { 
    T OldValue { get; set; }

    T NewValue { get; set; }

    TrackedPropertyType PropertyType { get; set; }

}



public enum TrackedPropertyType
{
    PhoneNumber,
    Name,
    Email,
    BirthDate,
    WorkedInDodo,
    SocialNetworkMessagingEnable,
    IsKeyWord,
    IsBanned,
    IsFraud,
    IsRemove,
}

The problem is specifically that "object" is a type, and <T> can be a different type than object declaration wise.

The deserializer does not know what to expect.

If you force the values to be the declaring type <T>, you will not confuse the serializer. I am trying to convey this is simply as possible, and realize this might not be 100% accurate. But should suffice for now.

Edit 2: If you, for some odd reason insist that the abstract keyword is used: (Also I am sorry, I am not really thinking it through anymore, just copy pasting, but seriously, it shouldn't be a problem to modify this example into whatever extended features you want.)

class Program
    {
        public static void Main(String[] args)
        {
            ChangedTestClass<TestClass> changedTestClass = new ChangedTestClass<TestClass>();

            changedTestClass.NewValue = new TestClass() { 
                Name = "Test class 2",
                Id = 2,
                PropertyType = TrackedPropertyType.Email
            };

            changedTestClass.OldValue = new TestClass() { 
                Name = "Test class 1",
                Id = 1,
                PropertyType = TrackedPropertyType.PhoneNumber
            };

            var stringResult = JsonSerializer.Serialize(changedTestClass);
            var result = JsonSerializer.Deserialize<ChangedTestClass<TestClass>>(stringResult);
           
        }
    }

    public abstract class Changed<T> : IValues<T>
    {
        public T OldValue { get; set; }
        public T NewValue { get; set; }
        public TrackedPropertyType PropertyType { get; set; }
    }

    public class ChangedTestClass<TestClass> {
        public TestClass OldValue { get; set; }
        public TestClass NewValue { get; set; }
        public TrackedPropertyType PropertyType { get; set; }
    }


    public class TestClass
    {
        public string Name { get; set; }
        public int Id { get; set; }

        public TrackedPropertyType PropertyType { get; set; }
    }
    
    public interface IValues<T> { 
        T OldValue { get; set; }

        T NewValue { get; set; }

        TrackedPropertyType PropertyType { get; set; }

    }

    

    public enum TrackedPropertyType
    {
        PhoneNumber,
        Name,
        Email,
        BirthDate,
        WorkedInDodo,
        SocialNetworkMessagingEnable,
        IsKeyWord,
        IsBanned,
        IsFraud,
        IsRemove,
    }