当前位置: 首页 > article >正文

Unity序列化多态数组

文档

Json序列化
脚本序列化

问题

Unity序列化数组时,只能存储基类内容,子类内容缺少。
Unity版本 2019.4.40
原因:Unity序列化不支持多态

测试类
将testarray类序列化时,多态列表personlist只转换了基类数据,子类数据没有转换

    public class Person
    {
        public string name = "1";
    }
    [System.Serializable]
    public class Student : Person
    {
        public string grade = "2";
    }
    [System.Serializable]
    public class TestArray
    {
        public List<Person> personList;
    }

测试

private void Start()
{
    TestArray testArray = new TestArray();
    testArray.personList = new List<Person>() { new Person(), new Student() };
    var content = JsonUtility.ToJson(testArray);
    Debug.Log(content);
}

输出结果
student实例的内容grade没有转换

打印:{"personList":[{"name":"1"},{"name":"1"}]}

解决方法

将多态数组拆分为有具体类型的多个数组
相同类型的元素存储到相同类型的数组中,避免多态
优点:思路简单,效率高
缺点:没有了多态的特性

[System.Serializable]
public class Grade3
{
    public List<Student> students;
    public List<Person> persons;
}

解决方法1

JsonUtility可以将多态数组的元素完整转换
利用该特点,将多态数组的子元素转存为类型子元素对应的Json字符串
优点:思路简单
缺点:不同类型的多态数组都需要实现转换逻辑,效率低,频繁使用JsonUtility方法GC多
适用于:数据量较少,子类数量不固定

测试类
解释:自定义类实现ISerializationCallbackReceiver接口,
序列化之前,将多态数组中的元素保存为类型和数据字符串
反序列化之后,遍历类型和数据字符串,利用反射和json反序列化,将数据还原到到多态数组中

    [System.Serializable]
    public class Grade : ISerializationCallbackReceiver
    {
        [NonSerialized]
        public Person[] personList;
        [SerializeField]
        private string[] types;
        [SerializeField]
        private string[] datas;

        public void OnBeforeSerialize()//序列化之前
        {
            if (personList != null && personList.Length > 0)
            {
                var length = personList.Length;
                types = new string[length];
                datas = new string[length];
                for (int i = 0; i < length; i++)
                {
                    types[i] = personList[i].GetType().FullName;//存储子元素类型
                    datas[i] = JsonUtility.ToJson(personList[i]);//存储子元素转换的字符串
                }
            }
        }

        public void OnAfterDeserialize()
        {
            if (types != null && datas != null)
            {
                var length = types.Length;

                personList = new Person[length];

                Dictionary<string, Type> typeDic = new Dictionary<string, Type>();//缓存类型避免频繁获取

                for (int i = 0; i < length; i++)
                {
                    if (!typeDic.ContainsKey(types[i]))
                        typeDic.Add(types[i], Type.GetType(types[i]));
                    var type = typeDic[types[i]];
                    if (type != null && typeof(Person).IsAssignableFrom(type))
                    {
                        Person person = (Person)Activator.CreateInstance(type);//反射创建实例元素
                        JsonUtility.FromJsonOverwrite(datas[i], person);//反序列化数据存放到实例中
                        personList[i] = person;
                    }
                }
            }
        }
        
        public void Print()//打印数组元素
        {
            for (int i = 0; i < personList.Length; i++)
            {
                Debug.Log(personList[i]);
            }
        }
    }

测试

private void Start()
{
    var grade = new Grade();
    grade.personList = new Person[2] { new Person(), new Student() };
    var info = JsonUtility.ToJson(grade);
    Debug.Log(info);
}

输出结果
student实例的内容grade转换

打印:
{
"types":["Test.Person","Test.Student"],
"datas":["{\"name\":\"1\"}","{\"name\":\"1\",\"grade\":\"2\"}"]
}

解决方法2

优点:序列化速度比方法1快,保留多态特性
缺点:子类数量一旦过多,需要编写更多脚本
适用于子类数目较少的,不需要元素的顺序

测试类
解释:序列化时,将多态数组中的数据存放到对应类型的数组中,减少Json方法的调用
反序列化时,将对应数组中的数据存放到多态数组中

    public class Grade2 : ISerializationCallbackReceiver
    {
        [NonSerialized]
        public List<Person> runtimes;//不需要序列化多态数组

        [SerializeField] Student[] students;
        [SerializeField] Person[] persons;

        public void OnBeforeSerialize()
        {
            this.students = null;
            this.persons = null;

            if (runtimes != null && runtimes.Count > 0)
            {
                var length = runtimes.Count;
                int[] indexArray = new int[length];//索引数组

                int arrayLength = 0;
                Type targetType = typeof(Student);
                for (int i = 0; i < length; i++)
                {
                    if (runtimes[i].GetType() == targetType)
                    {
                        arrayLength++;
                        indexArray[i] = i;//存放当前Type对应元素的索引
                    }
                }
                if (arrayLength > 0)
                {
                    this.students = new Student[arrayLength];//直接设置长度
                    for (int i = 0; i < arrayLength; i++)
                        this.students[i] = runtimes[indexArray[i]] as Student;
                }

                arrayLength = 0;
                targetType = typeof(Person);
                for (int i = 0; i < length; i++)
                {
                    Type type = runtimes[i].GetType();
                    if (type == targetType)
                    {
                        arrayLength++;
                        indexArray[i] = i;
                    }
                }
                if (arrayLength > 0)
                {
                    this.persons = new Person[arrayLength];
                    for (int i = 0; i < arrayLength; i++)
                        this.persons[i] = runtimes[indexArray[i]];
                }
            }
        }

        public void OnAfterDeserialize()
        {
            int length = 0;
            if (students != null)
                length += students.Length;
            if (persons != null)
                length += persons.Length;

            runtimes = new List<Person>(length);//直接设置列表容量

            if (students != null)//填充
                runtimes.AddRange(students);
            if (persons != null)
                runtimes.AddRange(persons);
        }

        public void Print()
        {
            for (int i = 0; i < runtimes.Count; i++)
            {
                Debug.Log(runtimes[i]);
            }
        }
    }

测试

var grade2 = new Grade2();
grade2.runtimes = new List<Person> { new Person(), new Student() };
var info2 = JsonUtility.ToJson(grade2);
Debug.Log(info2);

输出结果

打印:{"students":[{"name":"1","grade":"2"}],"persons":[{"name":"1"}]}

解决方法3

使用高版本Unity的[SerializeReference]特性
优点:不需要编写多余内容,使用简单,效率比方法1高
缺点:序列化效率差,存储文件大,只支持高版本

测试类

    [System.Serializable]
    public class Grade4
    {
        [SerializeReference]//添加特性
        public List<Person> runtimes;
    }

测试

Grade4 grade4 = new Grade4();
grade4.runtimes = new List<Person> { new Person(), new Student() };
var info3 = JsonUtility.ToJson(grade);
Debug.Log(info3);

输出结果

{
"runtimes":[{"rid":1000},{"rid":1001}],
"references":{"version":2,"RefIds":[{"rid":1000,"type":{"class":"Person","ns":"Test","asm":"Assembly-CSharp"},"data":{"name":"1"}},{"rid":1001,"type":{"class":"Student","ns":"Test","asm":"Assembly-CSharp"},"data":{"name":"1","grade":"2"}}]}}

解决方法4

使用Newtonsoft.Json第三方包
优点:功能强大,支持多态数组,使用简单
缺点:第三方导入,效率不高

测试

TestArray testArray5 = new TestArray();
testArray5.personList = new List<Person>() { new Person(), new Student() };

JsonSerializerSettings settings = new JsonSerializerSettings();//序列化设置
settings.NullValueHandling = NullValueHandling.Ignore;//空值不保存
settings.TypeNameHandling = TypeNameHandling.Auto;//类型名称自动处理
settings.Formatting = Formatting.Indented;//格式化json文档
settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;//忽略循环引用
var content5 = JsonConvert.SerializeObject(testArray, settings);
Debug.Log(content5);

输出结果

打印
{
  "personList": [
    {
      "name": "1"
    },
    {
      "$type": "Test.Student, Assembly-CSharp",
      "grade": "2",
      "name": "1"
    }
  ]
}

http://www.kler.cn/a/547531.html

相关文章:

  • 当Ollama遇上划词翻译:我的Windows本地AI服务搭建日记
  • AI知识库 - Cherry Studio
  • C++基础知识(三)之结构体、共同体、枚举、引用、函数重载
  • 答题考试系统php+uniapp
  • 天童美语:观察你的生活
  • Windows 常用程序名
  • 知识蒸馏中的“温度系数“调控策略:如何让小模型继承大模型智慧?
  • 第六天:requests库的用法
  • 【前端进阶】「全面优化前端开发流程」:利用规范化与自动化工具实现高效构建、部署与团队协作
  • java枚举类型的查找
  • 沃德校园助手系统php+uniapp
  • 【16届蓝桥杯寒假刷题营】第1期DAY4
  • HTTP的“对话”逻辑:请求与响应如何构建数据桥梁?
  • 【Linux】:网络通信
  • SpringBoot3使用Swagger3
  • C++效率掌握之STL库:string底层剖析
  • Java-数据结构-(TreeMap TreeSet)
  • vue 文件下载(导出)excel的方法
  • 服务器虚拟化(详解)
  • zookeeper的zkCli.sh登录server报错【无法正常使用】