LitJson序列化反序列化支持多态(正确反序列化子类)

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、解决方案

  1. 多态标签
    定义一个 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;
    }
    
  2. 序列化时添加类信息
    在 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
    
  3. 反序列化时根据类信息创建对象
    在 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保留了。

代码详情参见: https://github.com/kimomi/LitJsonPolymorphism

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Java中的多态是指同一个方法名可以被不同的对象调用,而产生不同的行为结果。正确的关于Java类多态的说法有: 1. 多态是基于继承和接口实现的。子类可以继承父类的方法,并且可以重写这些方法,从而实现多态。 2. 在多态中,父类的引用可以指向子类的对象,从而实现对子类对象的访问和调用。 3. 在多态中,调用哪个方法是在运行期间决定的,而不是在编译期间决定的。这种运行期间的决定叫做动态绑定或运行时绑定。 4. 多态可以使代码更加灵活和可扩展,减少了代码的耦合性,提高了代码的可维护性和可读性。 5. 多态只适用于方法,不适用于属性。也就是说,无论父类引用指向哪个子类对象,访问的属性都是父类中定义的属性。 例如,下面是一个简单的Java多态的例子: ``` public class Animal { public void makeSound() { System.out.println("动物发出叫声"); } } public class Dog extends Animal { public void makeSound() { System.out.println("狗发出汪汪声"); } } public class Cat extends Animal { public void makeSound() { System.out.println("猫发出喵喵声"); } } public class Test { public static void main(String[] args) { Animal animal1 = new Dog(); Animal animal2 = new Cat(); animal1.makeSound(); animal2.makeSound(); } } 输出结果为: ``` 狗发出汪汪声 猫发出喵喵声 ``` 在上面的例子中,Animal是父类,Dog和Cat是Animal的两个子类。在main方法中,使用Animal类型的引用分别指向Dog和Cat的对象,然后分别调用它们的makeSound方法。由于动态绑定的存在,实际上会调用Dog和Cat的makeSound方法,从而产生不同的行为结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值