Search code examples
c#.netserializationjson.netsystem.text.json

Serialising/deserialising class with a abstract base loses value with Newtonsoft and System.Text.Json


I'm trying to serialise and deserialise a servicebus event class, my event inherits from an abstract base with a generic parameter for the payload, and my base class implements an interface.

No matter what options I have tried in both Newtonsoft and System.Text.Json, I cannot get this working. The serialise doesn't serialise the inner payload, and the deserialise throws this error:

private async Task ProcessMessageAsync<T>(ProcessMessageEventArgs args)
{
    try
    {
        var eventTypeInstance = typeof(T);

        var options = new JsonSerializerSettings(){
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            Converters = new List<Newtonsoft.Json.JsonConverter> { new StringEnumConverter() }
        };

        var body = Encoding.UTF8.GetString(args.Message.Body.ToArray());

        if (eventTypeInstance != null)
        {
            var eventInstance = JsonConvert.DeserializeObject(body, typeof(T), options);
        }
    }
}

Newtonsoft.Json.JsonSerializationException: 'Could not create an instance of type ServiceableBus.IServiceableBusPayload. Type is an interface or abstract class and cannot be instantiated. Path 'payload.field1', line 5, position 17.'

<T> is my concrete type.

Below are my class definitions:

public class TestEvent : ServiceableBusEvent<IServiceableBusPayload>
{
    public const string Topic = "test-event";

    public record TestEventPayload(string Field1, int Field2, int Field3) : IServiceableBusPayload;
}

public abstract class ServiceableBusEvent<T> : IServiceableBusEvent where T : IServiceableBusPayload
{
    [JsonPropertyName("messageTypeName")]
    public string MessageTypeName { get; set; }

    [JsonPropertyName("createdAt")]
    public DateTime CreatedAt { get; set; }

    [JsonPropertyName("payload")]
    public T Payload { get; set; }
}

public interface IServiceableBusEvent
{
}

public interface IServiceableBusPayload
{
}

Solution

  • Since TestEvent extends ServiceableBusEvent<IServiceableBusPayload>, the type of the Payload property is IServiceableBusPayload, so the deserializer has no way to know which concrete type you want.

    Make TestEvent extend ServiceableBusEvent<TestEventPayload> so it knows to use that specific type for the Payload property.