Search code examples
c#serializationjson.netjson-deserialization

Using JsonConvert.DeserializeObject to deserialize a list of derived objects


I have this Object

public class ConversationAPI
{
    [JsonProperty(PropertyName = "lU")]
    public DateTime LastUpdated { get; set; }

    [JsonProperty(PropertyName = "m", TypeNameHandling = TypeNameHandling.All)]
    public List<Message> Messages { get; set; }
}

Which I send from the API as a json and I deserialize in my Client Application.

The List<Message> Messages property contains either 

 [Serializable]
    public class Message
    {
        [JsonProperty(PropertyName = "t")]
        public string Text { get; set; }

        [JsonProperty(PropertyName = "ty")]
        public MessageType Type { get; set; }
    }

or

[Serializable]
    public class DerivedMessage : Message
    {
        [JsonProperty(PropertyName = "sos")]
        public string SomeOtherStuff{ get; set; }
    }

I can't seem to be able to deserialize the Array of derived types. I've tried this

var settings = new JsonSerializerSettings
                    {
                        TypeNameHandling = TypeNameHandling.All,
                        TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full
                    };
var conversation = JsonConvert.DeserializeObject<ConversationResponse>(response.Content, settings);

I would like the List Messages to have both Message and DerivedMessage objects.

Any ideas? Thanks


Solution

  • Found the solution. I used a custom converter

    public class MessageConverter : JsonCreationConverter<ConversationAPI.Message>
    {
        private const string SomeOtherStuffField = "sos";
    
        protected override ConversationAPI.Message Create(Type objectType, JObject jObject)
        {
            if (FieldExists(SomeOtherStuffField , jObject))
            {
                return new ConversationAPI.DerivedMessage ();
            }
    
            return new ConversationAPI.Message();
        }
    
        private bool FieldExists(string fieldName, JObject jObject)
        {
            return jObject[fieldName] != null;
        }
    }
    
    public abstract class JsonCreationConverter<T> : JsonConverter
    {
        /// <summary>
        /// Create an instance of objectType, based properties in the JSON object
        /// </summary>
        /// <param name="objectType">type of object expected</param>
        /// <param name="jObject">contents of JSON object that will be deserialized</param>
        /// <returns></returns>
        protected abstract T Create(Type objectType, JObject jObject);
    
        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            // Load JObject from stream
            JObject jObject = JObject.Load(reader);
    
            // Create target object based on JObject
            T target = Create(objectType, jObject);
    
            // Populate the object properties
            serializer.Populate(jObject.CreateReader(), target);
    
            return target;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    You would use this like so:

    var jsonText = "{a string of json to convert}"
    JsonConverter[] conv = new JsonConverter[] { new MessageConverter() };
    var jsonResponse = JsonConvert.DeserializeObject<ConversationAPI>(jsonText, conv);