Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
366 views
in Technique[技术] by (71.8m points)

c# - Using custom JsonConverter and TypeNameHandling in Json.net

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

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

I also have multiple concrete implementations of the IBar interface that can be set at runtime. Some of these concrete classes require a custom JsonConverter for serialization & deserialization.

Utilizing the TypeNameHandling.Auto option the non-convertor requiring IBar classes can be serialized and deserialized perfectly. The custom-serialized classes on the other hand have no $type name output and while they are serialized as expected, they cannot be deserialized to their concrete type.

I attempted to write-out the $type name metadata myself within the custom JsonConverter; however, on deserialization the converter is then being bypassed entirely.

Is there a workaround or proper way of handling such a situation?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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<string>("$type");

        if (type == GetTypeString<BarA>())
        {
            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<T>()
    {
        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<BarA>(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<JsonConverter>();
        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<Foo>(json, JsonHelper.DefaultSerializerSettings);

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...