使用反射使Newtonsoft Json.NET支持多态反序列化

最近在使用Newtonsoft Json的时候发现其在反序列化时,不会生成其序列化时的子类,而只会反序列化到我们提供的类,也就是说对象丢失了子类的信息,这样就会出现很多问题。

尝试直接进行序列化、反序列化

比如有以下类图:
使用反射使Newtonsoft Json.NET支持多态反序列化_第1张图片

然后有以下代码:

var file = new ChatFile()
{
    FileName = "file",
    Text = "Text"
};

var json = JsonConvert.SerializeObject(message);
File.WriteAllText(@"C:\Users\13553\Desktop\123\log.json",json);

其序列化后的json字符串为

{
    "FileName": "file",
    "Text": "Text"
}

然后我们去反序列化字符串

var message = JsonConvert.DeserializeObject(json);
Debug.WriteLine(message.GetType());

输出为JsonDemo.ChatMessage

想了想马上就明白了,反序列化时,转换器不知道类原来是哪个类,当然只会反序列化到父类

开始改造

既然不知道是什么子类,那么只需要将子类的类名保存下来就行了,也就是使用反射创造对象

  1. 首先我们需要一个ChatMessageJsonObject,保存类名的TypeName还有原对象
private class ChatMessageJsonObject
{
    public string TypeName { get; }

    public ChatMessage Value { get; }

    public ChatMessageJsonObject(ChatMessage message)
    {
        Value = message;
        TypeName = Value.GetType().AssemblyQualifiedName;
    }
}
  1. 创建一个ChatMessageCoverter,实现JsonConverter,然后重写ReadJson方法

    • 通过TypeName得知类名
    • 通过Type.GetType(typeName)获取类
    • 通过Activator.CreateInstance(type)创建对象
    • 通过serializer.PopulateValue写入信息
private class ChatMessageCoverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, ChatMessageJsonObject value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override ChatMessageJsonObject ReadJson(JsonReader reader, Type objectType, ChatMessageJsonObject existingValue, bool hasExistingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var typeName = jsonObject["TypeName"].ToString();

        // 通过反射创建子类,再转回父类
        var type = Type.GetType(typeName);
        var target = (ChatMessage)Activator.CreateInstance(type);

        // 写入对象信息
        serializer.Populate(jsonObject["Value"].CreateReader(),target);
        return new ChatMessageJsonObject(target);
    }
}
  1. 使用ChatMessageCoverter
// 序列化
var target = new ChatMessageJsonObject(message);
var json = JsonConvert.SerializeObject(target);

// 反序列化
var message = JsonConvert.DeserializeObject(json,new ChatMessageCoverter());
Debug.WriteLine(message.GetType());

其序列化的JSON字符串为

{
    "TypeName": "JsonDemo.ChatFile, JsonDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
    "Value": {
        "FileName": "file",
        "Text": "Text"
    }
}

反序列化为JsonDemo.ChatFile

至此,多态的问题就解决了。

另一种办法

今天查官方文档,发现JsonSerializerSettings中有个属性TypeNameHandling,将其设置为TypeNameHandling.All之后就可以完成和我上面一模一样的操作

var Settings = new JsonSerializerSettings()
{
    TypeNameHandling = TypeNameHandling.All
};

// 序列化
var json = JsonConvert.SerializeObject(message,Settings);

// 反序列化
var message = JsonConvert.DeserializeObject(json,Settings);

其序列化的字符串也是包含了一个$type属性,应该是一个思路

{
    "$type": "JsonDemo.ChatFile, JsonDemo",
    "FileName": "file",
    "Text": "Text"
}

不过借此加强了对C#反射的运用,还是不亏的

你可能感兴趣的:(使用反射使Newtonsoft Json.NET支持多态反序列化)