Using custom JsonConverter and TypeNameHandling in Json.net

前端 未结 2 1757
没有蜡笔的小新
没有蜡笔的小新 2020-12-04 01:15

I have a class with an interface-typed property like:

public class Foo
{
    public IBar Bar { get; set; }
}

I also have multiple concrete

2条回答
  •  攒了一身酷
    2020-12-04 02:19

    I solved the similar problem and I found a solution. It's not very elegant and I think there should be a better way, but at least it works. So my idea was to have JsonConverter per each type that implements IBar and one converter for IBar itself.

    So let's start from models:

    public interface IBar { }
    
    public class BarA : IBar  { }
    
    public class Foo
    {
        public IBar Bar { get; set; }
    }
    

    Now let's create converter for IBar. It will be used only when deserializing JSON. It will try to read $type variable and call converter for implementing type:

    public class BarConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotSupportedException();
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jObj = JObject.Load(reader);
            var type = jObj.Value("$type");
    
            if (type == GetTypeString())
            {
                return new BarAJsonConverter().ReadJson(reader, objectType, jObj, serializer);
            }
            // Other implementations if IBar
    
            throw new NotSupportedException();
        }
    
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof (IBar);
        }
    
        public override bool CanWrite
        {
            get { return false; }
        }
    
        private string GetTypeString()
        {
            var typeOfT = typeof (T);
            return string.Format("{0}, {1}", typeOfT.FullName, typeOfT.Assembly.GetName().Name);
        }
    }
    

    And this is converter for BarA class:

    public class BarAJsonConverter : BarBaseJsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            // '$type' property will be added because used serializer has TypeNameHandling = TypeNameHandling.Objects
            GetSerializer().Serialize(writer, value);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var existingJObj = existingValue as JObject;
            if (existingJObj != null)
            {
                return existingJObj.ToObject(GetSerializer());
            }
    
            throw new NotImplementedException();
        }
    
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(BarA);
        }
    }
    

    You may notice that it's inherited from BarBaseJsonConverter class, not JsonConverter. And also we do not use serializer parameter in WriteJson and ReadJson methods. There is a problem with using serializer parameter inside custom converters. You can read more here. We need to create new instance of JsonSerializer and base class is a good candidate for that:

    public abstract class BarBaseJsonConverter : JsonConverter
    {
        public JsonSerializer GetSerializer()
        {
            var serializerSettings = JsonHelper.DefaultSerializerSettings;
            serializerSettings.TypeNameHandling = TypeNameHandling.Objects;
    
            var converters = serializerSettings.Converters != null
                ? serializerSettings.Converters.ToList()
                : new List();
            var thisConverter = converters.FirstOrDefault(x => x.GetType() == GetType());
            if (thisConverter != null)
            {
                converters.Remove(thisConverter);
            }
            serializerSettings.Converters = converters;
    
            return JsonSerializer.Create(serializerSettings);
        }
    }
    

    JsonHelper is just a class to create JsonSerializerSettings:

    public static class JsonHelper
    {
        public static JsonSerializerSettings DefaultSerializerSettings
        {
            get
            {
                return new JsonSerializerSettings
                {
                    Converters = new JsonConverter[] { new BarConverter(), new BarAJsonConverter() }
                };
            }
        }
    }
    

    Now it will work and you still can use your custom converters for both serialization and deserialization:

    var obj = new Foo { Bar = new BarA() };
    var json = JsonConvert.SerializeObject(obj, JsonHelper.DefaultSerializerSettings);
    var dObj = JsonConvert.DeserializeObject(json, JsonHelper.DefaultSerializerSettings);
    

提交回复
热议问题