Json序列化反序列化支持多态
本文章的Json序列化使用LitJson库,当然使用其他库基本思路也是一样的。
1、LitJson多态序列化反序列化现在的不足
基础的序列化和反序列化测试代码如下:
using LitJson;
using System;
[Serializable]
public class ClassBaseA
{
public int a;
}
[Serializable]
public class ClassExtendB : ClassBaseA
{
public int b;
}
[Serializable]
public class SerializeClass
{
public int normalField = 1;
public ClassBaseA specialFiled;
}
class Program
{
public static void Main(string[] args)
{
var b = new ClassExtendB() {a = 1, b = 2};
SerializeClass toJsonObject = new SerializeClass() {specialFiled = b};
// 序列化
var jsonString = LitJson.JsonMapper.ToJson(toJsonObject);
Console.WriteLine(jsonString);
// 反序列化
SerializeClass fromJsonObject = LitJson.JsonMapper.ToObject<SerializeClass>(jsonString);
Console.WriteLine(LitJson.JsonMapper.ToJson(fromJsonObject));
}
}
得到的结果为:
{“normalField”:1,“specialFiled”:{“b”:2,“a”:1}}
{“normalField”:1,“specialFiled”:{“a”:1}}
我们的字段 specialFiled.b
丢失了,这是由于类 SerializeClass
里面定义的字段 specialFiled
类型为 ClassBaseA
,所以对应的字段 b
被忽略了。根本原因是 LitJson 是使用反射来获取类中的字段类型,并且发现序列化Json字符串中含有未知字段会直接跳过,具体可见 JsonMapper.cs 源码。
1、序列化
使用LitJson库进行Json的序列化,默认支持多态的,所以导出部分看起来是正常的。
2、反序列化
LitJson反序列化是使用反射实现的,代码可以参考 JsonMapper.cs 的函数
private static object ReadValue(Type inst_type, JsonReader reader, bool skipFirstRead = false)
基本思路就是反射获取类里面的 Field 和 Property ,根据Json字符串里面键和字段名进行匹配,然后再把Json字符串里面的值转换为字段对应类型的数据。
3、修改思路
其中的问题在,反序列化是通过反射直接获取的,因此传入的子类Json数据(示例里的ClassExtendB
)里只有原始类(示例里的SerializeClass
)定义时的类(示例里的ClassBaseA
)里面的 Field 和 Property 才能正确序列化,其余数据都会被丢弃。所以,我们只要把反序列化时反射获取定义的类(ClassBaseA
)变成获取实际类(ClassExtendB
)就可以了。
4、解决方案
-
多态标签
定义一个 Attribute 标明是不是要对该类应用多态序列化和反序列化namespace LitJson { using System; /// <summary> /// 该类序列化与反序列化是否多态 /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] public class PolymorphismJsonAttribute : Attribute { } }
感谢评论中的朋友提醒,修改成可以判断接口是否含有 Attribute,函数定义为:
// 判断某个类、接口是否有 PolymorphismJsonAttribute 属性 private static bool TypeIsPolymorphismJsonAttribute(Type t) { bool hasAttribute = System.Attribute.GetCustomAttribute(t, typeof(PolymorphismJsonAttribute), true) != null; if (hasAttribute) return true; foreach (var interfaceType in t.GetInterfaces()) { if (System.Attribute.GetCustomAttribute(interfaceType, typeof(PolymorphismJsonAttribute), true) != null) { hasAttribute = true; break; } } return hasAttribute; }
-
序列化时添加类信息
在 JsonMapper.cs 的 WriteValue 函数里面,序列化一个Object时,如果有 PolymorphismJsonAttribute 就把该类的实际程序集名和类名记录下来,这样就可以在反序列化时使用了。我加的代码在两段 add code for PolymorphismJsonAttribute 内,其余部分为 LitJson 源码。// LitJson something else writer.WriteObjectStart(); // add code for PolymorphismJsonAttribute begin // 打了多态序列化标签的话,就记录对应数据 if (TypeIsPolymorphismJsonAttribute(obj_type)) { writer.WritePropertyName("__ASSEMBLY__"); writer.Write(obj_type.Assembly.FullName); writer.WritePropertyName("__TYPE__"); writer.Write(obj_type.FullName); } // add code for PolymorphismJsonAttribute end // LitJson something else (wrirte object real) writer.WriteObjectEnd(); // LitJson something else
-
反序列化时根据类信息创建对象
在 JsonMapper.cs 的 ReadValue 函数里面, 反序列化一个 object,如果有 PolymorphismJsonAttribute 这个标签就用对应的数据(__ASSEMBLY__
和__TYPE__
)来确定实际的类。// ReadValue 函数定义有修改,添加一个参数标记要不要跳过第一个读取操作 private static object ReadValue (Type inst_type, JsonReader reader, bool skipFirstRead = false) { if (!skipFirstRead) { reader.Read (); } // LitJson something else while (true) { // LitJson something else (Continuously read and break) if (t_data.Properties.ContainsKey(property)) { PropertyMetadata prop_data = t_data.Properties[property]; if (prop_data.IsField) { // add code for PolymorphismJsonAttribute begin // 这里是直接获取字段的 prop_data.Type, 但是对于需要使用多态的序列化而言,是有一定问题的 // 所以加个属性PolymorphismJsonAttribute,如果属性有定义,说明需要使用多态来进行处理 if (TypeIsPolymorphismJsonAttribute(prop_data.Type)) { reader.Read(); // ObjectStart reader.Read(); // __ASSEMBLY__ reader.Read(); string assembly = (string)reader.Value; reader.Read(); // __TYPE__ reader.Read(); string type = (string)reader.Value; Type t = Assembly.Load(assembly).GetType(type); reader.Token = JsonToken.ObjectStart; ((FieldInfo)prop_data.Info).SetValue(instance, ReadValue(t, reader, true)); // 这里第三个参数用来标记跳过读取操作 } else { ((FieldInfo)prop_data.Info).SetValue(instance, ReadValue(prop_data.Type, reader)); } // add code for PolymorphismJsonAttribute end } else { // LitJson something else } } // LitJson something else } // LitJson something else }
5、结果
只要像以上稍微改动一点,我们就可以正确显示结果了。在 ClassBaseA
上加上我们新定义的标签 PolymorphismJson
,其余保持不变。
[LitJson.PolymorphismJson]
public class ClassBaseA
{
public int a;
}
结果为:
{“normalField”:1,“specialFiled”:{"__ASSEMBLY__":“ConsoleApplication1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null”,"__TYPE__":“ClassExtendB”,“b”:2,“a”:1}}
{“normalField”:1,“specialFiled”:{"__ASSEMBLY__":“ConsoleApplication1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null”,"__TYPE__":“ClassExtendB”,“b”:2,“a”:1}}
结果多记录了类的程序集名和类名,我们想要的字段b
保留了。