JamesNK / Newtonsoft.Json

Json.NET is a popular high-performance JSON framework for .NET
https://www.newtonsoft.com/json
MIT License
10.84k stars 3.26k forks source link

Custom converter for derived types are not used when deserializing to parent/interface #301

Closed prokopst closed 10 years ago

prokopst commented 10 years ago

I tried to deserialize type derived from interface IComponent. Objects in JSON had defined $type correctly. Although this works flawlessly without custom converters, deserialization with custom converters does not work. I tried converter, but it is not called for ComponentA at all, only with IComponent. I also tried to use contract resolver - CreateContract is called for IComponent and then ComponentA, but if I set converter for ComponentA (as in example) it's completely ignored. However it's not ignored for IComponent. I've found the problem with List<IComponent> when none of my custom converters were called.

Does this library support this scenario? If it does not, could we implement it? :wink:

The code that illustrates the problem:

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.IO;
using System.Text;

namespace Playground
{
    public class ConverterContractResolver : DefaultContractResolver
    {
        protected override JsonContract CreateContract(Type objectType)
        {
            JsonContract contract = base.CreateContract(objectType);
            if (objectType == typeof(ComponentA))
            {
                // converter is ignored, but custom converter for IComponent is not
                contract.Converter = new ComponentAConverter();
            }
            return contract;
        }
    }

    public interface IComponent { }

    public class ComponentA : IComponent { }

    public class ComponentAConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(ComponentA) == objectType;
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }

    class Program
    {
        const string contentA = @"{ ""$type"": ""Playground.ComponentA, Playground"" }";
        static StreamReader CreateStringStream(string content)
        {
            var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
            return new StreamReader(stream);
        }

        static void Main(string[] args)
        {
            JsonSerializerSettings settings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.All
                // both don't work
                //, Converters = new JsonConverter[] { new ComponentAConverter() }
                , ContractResolver = new ConverterContractResolver()
            };
            var serializer = JsonSerializer.CreateDefault(settings);
            // should throw, but it does not
            var entity = (IComponent)serializer.Deserialize(CreateStringStream(contentA), typeof(IComponent));
        }
    }
}

Version I used: the latest from nuget.

JamesNK commented 10 years ago

It doesn't and it won't.