美文网首页
Unity技术沉淀--读取json数据(反序列化)

Unity技术沉淀--读取json数据(反序列化)

作者: 忆中异 | 来源:发表于2021-10-18 18:30 被阅读0次

    上一节讲到了如何借助第三方工具从excel数据表中导出json数据。Execl表格导出json、lua

    Json的序列化和反序列化相关的第三方工具非常多,litjson,fastjson等等。

    JsonUtility

    1.特点

    JsonUtility 是unity 官方5.3以后推出的工具,其主要的特点有

    • 效率高
    • 不依赖第三方库

    2.局限性

    • 无法直接序列化和反序列化List<T>和Dictionary<TKey,TValue>
    • 3.0自动属性也无法序列和反序列化

    实际上List<T>是支持的,只是无法直接的对List<T>对象进行序列化和反序列化,需要将List<T>包装(Wrap)到一个类当中,然后序列化和反序列化包含这个List<T>的对象即可


    Dictionary是确实不支持的,这在官方的文档中也有说明
    https://docs.unity3d.com/Manual/JSONSerialization.html

    对于JsonUtility无法序列化和反序列化List<T>和Dictionary<TKey,TValue>的情况,大家如果去搜索相关的资料,应该都会找到下面链接中的解决方案:

    http://kou-yeung.hatenablog.com/entry/2015/12/31/014611

    List<T>,无法直接序列化,将它包装在一个类中就可以了

    比如,下面这样是无法序列化的:(我直接使用参考文档中的代码)

    [Serializable] 
    public class Enemy 
    { 
        public string name; 
        public List<string> skills; 
        public Enemy(string name, List<string> skills) 
        { 
            this.name = name; 
            this.skills = skills; 
        } 
    }
    
    //序列化
    var enemies = new List<Enemy>(); 
    enemies.Add(new Enemy("Json", new List<string>() { "Attack" })); 
    enemies.Add(new Enemy("Kate", new List<string>() { "Attack", "Defence" })); 
    Debug.Log(JsonUtility.ToJson(enemies));
    

    输出的内容为空,无法序列化enemies到Json
    解决方案:
    我们只需要将var enemies = new List<Enemy>();包装到一个类中即可

    public class EnemyWrap 
    { 
        public List<Enemy> enemies = new List<Enemy>(); 
        public EnemyWrap() 
        {  
            enemies.Add(new Enemy("Json", new List<string>() { "Attack" })); 
            enemies.Add(new Enemy("Kate", new List<string>() { "Attack", "Defence" })); 
        } 
    } 
    

    放在EnemyWrap类中,再进行序列化操作:

    EnemyWrap enemy = new EnemyWrap(); 
    Debug.Log(JsonUtility.ToJson(enemy));
    

    输出结果

    {"enemies":[{"name":"Json","skills":["Attack"]},{"name":"Kate","skills":["Attack","Defence"]}]}
    

    下面是它封装好的泛型类List<T>:

    // List<T> 
    [Serializable] 
    public class Serialization<T> 
    { 
        [SerializeField] 
        List<T> target; 
        public List<T> ToList() { return target; } 
        public Serialization(List<T> target) 
        { 
            this.target = target; 
        } 
    } 
    

    和上面EnemyWrap类是相同的处理方式,只是字段变成了target
    JsonUtility不支持Dictionary<TKey,TValue>的序列化和反序列化,上面的网址中提供的方式其实是将Dictionary<TKey,TValue>分成了两个List<T>实现

    // Dictionary<TKey, TValue> 
    [Serializable] 
    public class Serialization<TKey, TValue> : ISerializationCallbackReceiver 
    { 
        [SerializeField] 
        List<TKey> keys; 
        [SerializeField] 
        List<TValue> values; 
        Dictionary<TKey, TValue> target; 
        public Dictionary<TKey, TValue> ToDictionary() { return target; } 
        public Serialization(Dictionary<TKey, TValue> target) 
        { 
            this.target = target; 
        } 
        public void OnBeforeSerialize() 
        { 
            keys = new List<TKey>(target.Keys); 
            values = new List<TValue>(target.Values); 
        } 
        public void OnAfterDeserialize() 
        { 
            var count = Math.Min(keys.Count, values.Count); 
            target = new Dictionary<TKey, TValue>(count); 
            for (var i = 0; i < count; ++i) 
            { 
                target.Add(keys[i], values[i]); 
            } 
        } 
    } 
    

    List<TKey> keys;
    List<TValue> values;
    拆成了两个List,分别存放key和value
    这里实现了ISerializationCallbackReceiver,由Unity提供,只有两个接口:

    void OnAfterDeserialize(); 
    void OnBeforeSerialize(); 
    

    顾名思义,OnAfterDeserialize在序列化完成之后调用
    OnBeforeSerialize在序列化开始之前调用
    在OnBeforeSerialize序列化开始之前,将传入进来的Dictionary参数的keys,values存放到
    两个List<T>当中
    在OnAfterDeserialize序列化完成后,将反序列化回来的两个List,通过for循环的方式还原成
    Dictionary<TKey,TValue>
    这样序列出来的结果如下:

    {"keys":[1000,2000],"values":[{"name":"怪物1","skills":["攻击"]},{"name":"怪物2","skills":["攻击","恢复"]}]}  
    

    可以看到,里面存放了两个数组keys[…],values[…]
    如果你要在实际的项目中,使用上面的Dictionary<Tkey,TValue>,要记得包装(Wrap)一个类,派生自Serialization<TKey, TValue>,这样才会有效

    [Serializable] 
    public class SampleDictionary : Serialization<int, int> 
    { 
        public SampleDictionary(Dictionary<int, int> targe) : base(targe) { } 
    }
    

    对于作者提供的Dictionary<TKey,TValue>方案,需要我们在过程中注意Dictionary的实现是由两个List<T>组成的。
    通常来讲,我们使用Dictonary<TKey,TValue>主要是哈希的查找效率,如果不是对大量数据进行频繁的查找,那么可以使用数组来替代Dictionary<TKey,TValue>,或是你的数据结构比较简单,
    可以以字符串的形式,通过特定的标志,在OnAfterDeserialize接口, 将字符串分割再转换成Dictionary<Tkey,TValue>等等
    目前使用JsonUtility来处理Dictionary<Tkey,TValue>是要二次转换的,总之要在“规则”之下使 ,具体项目中也要根据需求来决定是否采用。
    对于不参与序列化或反序化的字段,可以添加特性[NonSerialized]

    全序列化FullSerializer

    下载链接Github–FullSerializer插件下载链接

    FullSerializer 是一个易于使用且健壮的 JSON 序列化器。它几乎可以序列化任何你可以扔给它的东西,并在每个主要的 Unity 平台上工作,包括控制台。
    最重要的是,Full Serializer 完全免费使用

    用法

    用法与JsonUtility序列化相同,不必将类型标记为[Serializable].

    struct SerializedStruct {
        public int Field;
        public Dictionary<string, string> DictionaryAutoProperty { get; set; }
        [SerializeField]
        private int PrivateField;
    }
    

    序列化默认的规则

    • 公共变量默认序列化
    • 至少部分公开的自动属性默认被序列化
    • 所有用[SerializeField]或被[fsProperty]序列化的字段或属性
    • 如果用[NonSerialized]或注释,公共字段/公共自动属性不会被序列化[fsIgnore]。[fsIgnore]可用于属性(与 不同[NonSerialized])。

    FullSerializer 会自动处理继承和循环对象图。你不需要做任何事情。
    以下代码是我常用的将对象序列化为字符串或从字符串序列化成对象。

    using System;
    using FullSerializer;
    
    
    public static class FullSerializerAPI
    {
        private static readonly fsSerializer _serializer = new fsSerializer();
    
        public static string Serialize(Type type, object value, bool isPretty = false,bool isEncryption = true)
        {
            // serialize the data
            fsData data;
            _serializer.TrySerialize(type, value, out data).AssertSuccessWithoutWarnings();
    
            // emit the data via JSON
            if (isPretty)
            {
                string jsonStr = fsJsonPrinter.PrettyJson(data);
                if(isEncryption)
                    jsonStr = StringEncryption.EncryptDES(jsonStr);   //加密
                return jsonStr;
            }
            else
            {
                string jsonStr = fsJsonPrinter.CompressedJson(data);
                if (isEncryption)
                    jsonStr = StringEncryption.EncryptDES(jsonStr);   //加密
                return jsonStr;
                //return fsJsonPrinter.CompressedJson(data);
            }
        }
    
        public static object Deserialize(Type type, string serializedState)
        {
            serializedState = StringEncryption.DecryptDES(serializedState); //////解密
            // step 1: parse the JSON data
            fsData data = fsJsonParser.Parse(serializedState);
    
            // step 2: deserialize the data
            object deserialized = null;
            _serializer.TryDeserialize(data, type, ref deserialized).AssertSuccessWithoutWarnings();
    
            return deserialized;
        }
    
        public static T Deserialize<T>(string jsonStr) where T : new()
        {
            fsData data = fsJsonParser.Parse(jsonStr);
            T deserialized = new T();
            _serializer.TryDeserialize(data, ref deserialized);
            return deserialized;
        }
    }
    

    以下举例说明(文本可以是.txt文本格式,也可以使.json格式或者是你自己定义的某种格式)
    JSON CODE

    [
      {
        "Id": 1.0,
        "type": 1.0,
        "name": "红瓶",
        "desc": "加血药瓶",
        "prefabName": "prop_01"
      }
      {
        "Id": 2.0,
        "type": 1.0,
        "name": "蓝瓶",
        "desc": "加蓝药瓶",
        "prefabName": "prop_02"
      }
    ]
    

    我们如果把上面json通过FullSerializer工具反序列化成Dictionary对象呢?
    首先我们需要定义一个类作为反序列化对象

    [System.Serializable]
        public class Prop 
        {
            public readonly int Id;
            public readonly int type;
            public readonly string name;
            public readonly string desc;
            public readonly string prefabName;
        }
    

    然后通过代码读取json文本数据,这里用Resources.Load来读取,其他读取方式大家可以自行百度。

    TextAsset textAsset = Resources.Load<TextAsset>(Files.prop);
    string jsonStr = textAsset.text;
    List<Prop> props = FullSerializerAPI.Deserialize(typeof(List<Prop>), jsonStr) as List<Prop>;
    Dictionary propDict = props.ToDictionary(key => key.Id,value =>value);
    

    如果说定义中的某些变量没有出现在json code中我们应该怎么处理呢?这里推荐下FullSerializer转换器的高级定制:

    FullSerializer转换器支持对对象序列化的完全自定义。每个转换器都表示对它想要序列化的类型感兴趣;有两种方法可以做到这一点。FullSerializer中存在更强大(但速度较慢)的方法fsConverter,它通过函数回调确定它是否感兴趣,并且fsDirectConverter,它直接指定它将接管转换的对象类型。fsDirectConverter的主要限制fsDirectConverter是它不处理继承。

    这里列举其中一种转化器实例,其他方案大家可以自行百度。
    我们来看看这个模型:

    public class Person {
        public string FirstName;
        public string LastName;
        public int Age;
    }
    

    我们想要序列化它,但我们希望序列化的数据看起来像这样:

    {
        "Name": "John Doe",
        "Age": 25
    }
    

    转化为这个模型实例:

    var person = new Person {
        FirstName = "John",
        LastName = "Doe",
        Age = 25
    };
    

    本质上,当我们处理 的实例时Person,我们希望将其序列化为单个字符串字段,即连接名字和姓氏。

    这是转换器:

    using System;
    using System.Collections.Generic;
    using FullSerializer;
    
    public class PersonConverter : fsDirectConverter<Person> {
        public override object CreateInstance(fsData data, Type storageType) {
            return new Person();
        }
    
        protected override fsResult DoSerialize(Person model, Dictionary<string, fsData> serialized) {
            // Serialize name manually
            serialized["Name"] = new fsData(model.FirstName + " " + model.LastName);
    
            // Serialize age using helper methods
            SerializeMember(serialized, null, "Age", model.Age);
    
            return fsResult.Success;
        }
    
        protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref Person model) {
            var result = fsResult.Success;
    
            // Deserialize name mainly manually (helper methods CheckKey and CheckType)
            fsData nameData;
            if ((result += CheckKey(data, "Name", out nameData)).Failed) return result;
            if ((result += CheckType(nameData, fsDataType.String)).Failed) return result;
            var names = nameData.AsString.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            if (names.Length != 2) return result += fsResult.Fail("Too many names");
            model.FirstName = names[0];
            model.LastName = names[1];
    
            // Deserialize age using basically only helper methods
            if ((result += DeserializeMember(data, null, "Age", out model.Age)).Failed) return result;
    
            return result;
        }
    }
    

    FullSerializer转换器的限制

    Full Serializer 有最小的限制

    • WebPlayer 构建目标要求所有反序列化类型都具有默认构造函数
    • 没有多维数组支持(但是可以使用自定义转换器添加)

    相关文章

      网友评论

          本文标题:Unity技术沉淀--读取json数据(反序列化)

          本文链接:https://www.haomeiwen.com/subject/tnmnoltx.html