【Unity游戲開發】跟着馬三一起魔改LitJson


一、引子

  在游戲開發中,我們少不了和數據打交道,數據的存儲格式可謂是百花齊放,xml、json、csv、bin等等應有盡有。在這其中Json以其小巧輕便、可讀性強、兼容性好等優點受到廣大程序員的喜愛。目前市面上有許多針對Json類型數據的序列化與反序列化庫,比如Newtonsoft.Json、LitJson、SimpleJson、MiniJson等等,在這之中馬三比較鍾意於LitJson,其源碼規模適中、代碼規范可讀性好、跨平台能力強、解析速度快,但是美中不足的是LitJson對float(官方最新Release已經支持float)、以及Unity的Vector2、Vector3、Rect、AnimationCurve等類型不支持,譬如在解析float的時候會報 Max allowed object depth reached while trying to export from type System.Single 的錯誤,這就比較蛋疼了。

  通過閱讀LitJson源碼以后,馬三發現了改造LitJson以讓它支持更多屬性與行為的方法,而且目前全網關於LitJson改造的文章甚少,因此馬三決定通過本篇博客與大家分享一下改造LitJson的方法,權當拋磚引玉,造福奮斗在一線的程序員同志。

  改造后的LitJson版本可支持以下特性:

  • 支持 float
  • 支持 Vector2、Vector3、Vector4、Quaternion、Color、Bounds、Rect、AnimationCurve 等 Unity 特有的基本類型
  • 支持 JsonIgnore 跳過序列化 Attribute

二、下載LitJson源碼並熟悉結構

  我們的改造是基於 LitJson源碼的,所以首先要去獲取 LitJson源碼,從官方的Github上面直接選擇一個穩定的版本拉取下來就可以,馬三用的是2019年12月份的穩定版本。源碼Clone到本地以后,目錄是這個樣子:

 

   其中src目錄是LitJson的源碼部分,我們只要關注這里面的內容就可以了。src這個目錄下有10多個cs文件,在這里我們重點關注 JsonMapper.cs 、JsonReader.cs 、JsonWriter.cs就可以了。下面簡單介紹一下這三個類的作用和分工,具體的源碼分析我們會在后面講解。

  • JsonMapper 它的作用是負責將Json轉為Object或者從Object轉為Json,起到一個中轉器的作用,在里面有一系列的規則去告訴程序如何對Object進行序列化和對Json內容反序列化。它會去調用JsonReader和JsonWriter去執行具體的讀寫
  • JsonWriter 它的作用是負責將JsonMapper序列化好的Object文件寫到硬盤上,它里面包含了文件具體寫入時的一些規則
  • JsonReader 它的作用是負責將Json文件讀取並解析成一串JsonToken,然后再提供給JsonMapper使用

三、改造工作

  我們終於來到了激動人心的具體源碼改造環節了,別急,讓我們先搞清LitJson的工作原理。

1.分析序列化和反序列的具體原理

  在JsonMapper這個類中,有 base_exporters_table 和 base_importers_table 這兩個Dictionary,他們包含了LitJson內置的基本 Type 對應的序列化、反序列化規則,並且在JasonMapper的構造器中有 RegisterBaseImporters 和 RegisterBaseExporters 這兩個函數負責去注冊這些具體的導出導入規則行為。

  同時還有 custom_exporters_table 和 custom_importers_table 這兩個可供我們拓展的自定義序列化、反序列化行為規則,他們由 RegisterExporter 和 RegisterImporter 這兩個接口暴露給外部注冊。

  讓我先來看一下 RegisterBaseExporters 這個函數的代碼,以下是該函數的一些代碼片段:

 1 private static void RegisterBaseExporters()
 2         {
 3             base_exporters_table[typeof(byte)] =
 4                 delegate (object obj, JsonWriter writer)
 5                 {
 6                     writer.Write(Convert.ToInt32((byte)obj));
 7                 };
 8 
 9             base_exporters_table[typeof(char)] =
10                 delegate (object obj, JsonWriter writer)
11                 {
12                     writer.Write(Convert.ToString((char)obj));
13                 };
14 ...
15 }
View Code

  可看到在這個函數里面將byte 、char 、DateTime等較特殊的基本類型(為什么這里我們稱呼它們為較特殊的基本類型呢?因為Json里面是沒有byte 、char這些基本類型的,最后存儲的時候還是需要轉成int 、string這種Json所支持的基本類型)的數據序列化規則(一個delegate)注冊進了 base_exporters_table 這個Table中,以 byte 舉例,對於外界傳來的一個object類型的節點,會被強制成byte,然后再以int的形式由JsonWriter寫到具體的json文件中去。

  JsonMapper對外暴露了一系列序列化C#對象的接口,諸如 ToJson(object obj) 、ToJson(object obj, JsonWriter writer)等,這些函數實際最后都調用了 WriteValue 這個具體的負責寫入的函數,讓我們來看看WriteValue函數中的一些關鍵性代碼:

  1 private static void WriteValue(object obj, JsonWriter writer,
  2                                         bool writer_is_private,
  3                                         int depth)
  4         {
  5             if (depth > max_nesting_depth)
  6                 throw new JsonException(
  7                     String.Format("Max allowed object depth reached while " +
  8                                    "trying to export from type {0}",
  9                                    obj.GetType()));
 10 
 11             if (obj == null)
 12             {
 13                 writer.Write(null);
 14                 return;
 15             }
 16 
 17             if (obj is IJsonWrapper)
 18             {
 19                 if (writer_is_private)
 20                     writer.TextWriter.Write(((IJsonWrapper)obj).ToJson());
 21                 else
 22                     ((IJsonWrapper)obj).ToJson(writer);
 23 
 24                 return;
 25             }
 26 
 27             if (obj is String)
 28             {
 29                 writer.Write((string)obj);
 30                 return;
 31             }
 32 ...
 33 
 34             if (obj is Array)
 35             {
 36                 writer.WriteArrayStart();
 37 
 38                 foreach (object elem in (Array)obj)
 39                     WriteValue(elem, writer, writer_is_private, depth + 1);
 40 
 41                 writer.WriteArrayEnd();
 42 
 43                 return;
 44             }
 45 
 46             if (obj is IList)
 47             {
 48                 writer.WriteArrayStart();
 49                 foreach (object elem in (IList)obj)
 50                     WriteValue(elem, writer, writer_is_private, depth + 1);
 51                 writer.WriteArrayEnd();
 52 
 53                 return;
 54             }
 55 
 56             if (obj is IDictionary dictionary) {
 57                 writer.WriteObjectStart();
 58                 foreach (DictionaryEntry entry in dictionary)
 59                 {
 60                     var propertyName = entry.Key is string ? (entry.Key as string) : Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
 61                     writer.WritePropertyName(propertyName);
 62                     WriteValue(entry.Value, writer, writer_is_private,
 63                                 depth + 1);
 64                 }
 65                 writer.WriteObjectEnd();
 66 
 67                 return;
 68             }
 69 
 70             Type obj_type = obj.GetType();
 71 
 72             // See if there's a custom exporter for the object
 73             if (custom_exporters_table.ContainsKey(obj_type))
 74             {
 75                 ExporterFunc exporter = custom_exporters_table[obj_type];
 76                 exporter(obj, writer);
 77 
 78                 return;
 79             }
 80 
 81             // If not, maybe there's a base exporter
 82             if (base_exporters_table.ContainsKey(obj_type))
 83             {
 84                 ExporterFunc exporter = base_exporters_table[obj_type];
 85                 exporter(obj, writer);
 86 
 87                 return;
 88             }
 89 
 90             // Last option, let's see if it's an enum
 91             if (obj is Enum)
 92             {
 93                 Type e_type = Enum.GetUnderlyingType(obj_type);
 94 
 95                 if (e_type == typeof(long)
 96                     || e_type == typeof(uint)
 97                     || e_type == typeof(ulong))
 98                     writer.Write((ulong)obj);
 99                 else
100                     writer.Write((int)obj);
101 
102                 return;
103             }
104 
105             // Okay, so it looks like the input should be exported as an
106             // object
107             AddTypeProperties(obj_type);
108             IList<PropertyMetadata> props = type_properties[obj_type];
109 
110             writer.WriteObjectStart();
111             foreach (PropertyMetadata p_data in props)
112             {
113                 if (p_data.IsField)
114                 {
115                     writer.WritePropertyName(p_data.Info.Name);
116                     WriteValue(((FieldInfo)p_data.Info).GetValue(obj),
117                                 writer, writer_is_private, depth + 1);
118                 }
119                 else
120                 {
121                     PropertyInfo p_info = (PropertyInfo)p_data.Info;
122 
123                     if (p_info.CanRead)
124                     {
125                         writer.WritePropertyName(p_data.Info.Name);
126                         WriteValue(p_info.GetValue(obj, null),
127                                     writer, writer_is_private, depth + 1);
128                     }
129                 }
130             }
131             writer.WriteObjectEnd();
132         }
View Code

  首先做了一個解析層數或者叫解析深度的判斷,這個是為了防止相互依賴引用的情況下導致解析進入死循環或者爆棧。然后在這里處理了Int、String、Bool這些最基本的類型,首先從基本類型起開始檢測,如果匹配到合適的就用JsonWriter寫入。然后再去檢測是否是List、Dictionary等集合類型,如果是集合類型的話,會迭代每一個元素,然后遞歸調用WriteValue方法再對這些元素寫值。然后再去上文中我們說的自定義序列化(custom_exporters_table)規則里面檢索有沒有匹配項,如果沒有檢測到合適的規則再去內置的 base_exporters_table里面檢索,這里有一個值得注意的點,custom_exporters_table 的優先級是高於 base_exporters_table 的,所以我們可以在外部重新注冊一些行為來屏蔽掉內置的規則,這一點在將LitJson源碼打包成 dll 后,想修改內置規則又不用重新修改源碼的情況下非常好用。在上述規則都沒有匹配的情況下,我們一般會認為當前的object就是一個實實在在的對象了,首先調用 AddTypeProperties方法,將對象中的所有字段和屬性拿到,然后再依次地對這些屬性遞歸執行 WriteValue。

  分析完了序列化部分以后,我們再來看看反序列化的工作原理。序列化的有個WriteValue函數,那么按道理來講反序列化就該有一個ReadValue函數了,讓我們看看它的關鍵代碼:

private static object ReadValue(Type inst_type, JsonReader reader)
        {
            reader.Read();

            if (reader.Token == JsonToken.ArrayEnd)
                return null;

            Type underlying_type = Nullable.GetUnderlyingType(inst_type);
            Type value_type = underlying_type ?? inst_type;

            if (reader.Token == JsonToken.Null)
            {
#if NETSTANDARD1_5
                if (inst_type.IsClass() || underlying_type != null) {
                    return null;
                }
#else
                if (inst_type.IsClass || underlying_type != null)
                {
                    return null;
                }
#endif

                throw new JsonException(String.Format(
                            "Can't assign null to an instance of type {0}",
                            inst_type));
            }

            if (reader.Token == JsonToken.Double ||
                reader.Token == JsonToken.Int ||
                reader.Token == JsonToken.Long ||
                reader.Token == JsonToken.String ||
                reader.Token == JsonToken.Boolean)
            {

                Type json_type = reader.Value.GetType();

                if (value_type.IsAssignableFrom(json_type))
                    return reader.Value;

                // If there's a custom importer that fits, use it
                if (custom_importers_table.ContainsKey(json_type) &&
                    custom_importers_table[json_type].ContainsKey(
                        value_type))
                {

                    ImporterFunc importer =
                        custom_importers_table[json_type][value_type];

                    return importer(reader.Value);
                }

                // Maybe there's a base importer that works
                if (base_importers_table.ContainsKey(json_type) &&
                    base_importers_table[json_type].ContainsKey(
                        value_type))
                {

                    ImporterFunc importer =
                        base_importers_table[json_type][value_type];

                    return importer(reader.Value);
                }

                // Maybe it's an enum
#if NETSTANDARD1_5
                if (value_type.IsEnum())
                    return Enum.ToObject (value_type, reader.Value);
#else
                if (value_type.IsEnum)
                    return Enum.ToObject(value_type, reader.Value);
#endif
                // Try using an implicit conversion operator
                MethodInfo conv_op = GetConvOp(value_type, json_type);

                if (conv_op != null)
                    return conv_op.Invoke(null,
                                           new object[] { reader.Value });

                // No luck
                throw new JsonException(String.Format(
                        "Can't assign value '{0}' (type {1}) to type {2}",
                        reader.Value, json_type, inst_type));
            }

            object instance = null;

            if (reader.Token == JsonToken.ArrayStart)
            {

                AddArrayMetadata(inst_type);
                ArrayMetadata t_data = array_metadata[inst_type];

                if (!t_data.IsArray && !t_data.IsList)
                    throw new JsonException(String.Format(
                            "Type {0} can't act as an array",
                            inst_type));

                IList list;
                Type elem_type;

                if (!t_data.IsArray)
                {
                    list = (IList)Activator.CreateInstance(inst_type);
                    elem_type = t_data.ElementType;
                }
                else
                {
                    list = new ArrayList();
                    elem_type = inst_type.GetElementType();
                }

                while (true)
                {
                    object item = ReadValue(elem_type, reader);
                    if (item == null && reader.Token == JsonToken.ArrayEnd)
                        break;

                    list.Add(item);
                }

                if (t_data.IsArray)
                {
                    int n = list.Count;
                    instance = Array.CreateInstance(elem_type, n);

                    for (int i = 0; i < n; i++)
                        ((Array)instance).SetValue(list[i], i);
                }
                else
                    instance = list;

            }
            else if (reader.Token == JsonToken.ObjectStart)
            {
                AddObjectMetadata(value_type);
                ObjectMetadata t_data = object_metadata[value_type];

                instance = Activator.CreateInstance(value_type);

                while (true)
                {
                    reader.Read();

                    if (reader.Token == JsonToken.ObjectEnd)
                        break;

                    string property = (string)reader.Value;

                    if (t_data.Properties.ContainsKey(property))
                    {
                        PropertyMetadata prop_data =
                            t_data.Properties[property];

                        if (prop_data.IsField)
                        {
                            ((FieldInfo)prop_data.Info).SetValue(
                                instance, ReadValue(prop_data.Type, reader));
                        }
                        else
                        {
                            PropertyInfo p_info =
                                (PropertyInfo)prop_data.Info;

                            if (p_info.CanWrite)
                                p_info.SetValue(
                                    instance,
                                    ReadValue(prop_data.Type, reader),
                                    null);
                            else
                                ReadValue(prop_data.Type, reader);
                        }

                    }
                    else
                    {
                        if (!t_data.IsDictionary)
                        {

                            if (!reader.SkipNonMembers)
                            {
                                throw new JsonException(String.Format(
                                        "The type {0} doesn't have the " +
                                        "property '{1}'",
                                        inst_type, property));
                            }
                            else
                            {
                                ReadSkip(reader);
                                continue;
                            }
                        }

                      ((IDictionary)instance).Add(
                          property, ReadValue(
                              t_data.ElementType, reader));
                    }

                }

            }

            return instance;
        }
View Code

  首先外部會傳來一個JsonReader,它會按照一定的規則將Json的文本解析成一個一個JToken或者稱為JsonNode的這種節點,它對外暴露出了一個Read接口,每調用一次內部就會把迭代器向后一個節點推進一次。首先反序列化也是先從 int 、string、bool這些基本類型開始檢測,首先檢測 custom_importers_table中是否定義了匹配的import行為,如果沒有合適的再去base_importers_table里面索引。如果發現不是基本類型的話,就再依次按照集合類型、類等規則去檢測匹配,套路和Exporter的過程差不多,這里就不詳細展開了。同理base_importers_table都注冊有一些將ojbect轉為具體基本類型的規則。

2.支持float類型

  經過上面的一系列分析,我們可以很明顯的發現原生LitJson為何不支持對float類型的序列化和反序列了。在上述的 base_importers_table 、WriteValue和JsonWriter的重載Write方法中,我們都沒有找到與float類型匹配的規則和函數。因此只要我們把規則和函數補全,那么LitJson就可以支持float類型了。具體的代碼如下,為了方便跟原版對比,我這里用了Git diff信息的截圖,改造版的LitJson的完整源碼在文末有分享地址:

 

 

 

 

3.支持Vector2、Vector3、Vector4、Quaternion、Color、Bounds、Rect、AnimationCurve等Unity特有的基本類型

  有了上面改造LitJson支持float的基礎以后,再來新增對Vector2、Vecotr3等Unity內建類型的支持也就不是太大的難事了,只要針對這些類型編寫一套合適的Exporter規則就可以了,下面先以最簡單的Vector2舉例,Vector2在Unity中的定義如下:

   Vector2是Struct類型,它最主要的是x和y這兩個成員變量,觀察一下Vector2的構造器,在構造器里面傳入的也是 x 、y這兩個 float 類型的參數,因此我們只要想辦法將它按照一定的規則轉為Json的格式就可以了。C#中Struct的話,我們可以把它當成Json中的Object對象存儲,因此一個 Vector2 完全可以在Json中這樣去表示 {x : 10.0,y : 100.1}。制定了規則以后,就可以着手編寫相關代碼了,在 LitJson 中寫入一個對象之前需要先調用 JsonWriter.WriteObjectStart(),標記一個新對象的開始,然后依次執行 WritePropertyName 和 Write 函數,分別進行寫入鍵值,最后調用 WriteObjectEnd 標記這個對象已經寫完了,結束了。為了書寫方便,我們完全可以把 WritePropertyName 和 Write這兩個步驟封裝成一個好用的拓展方法,比如就叫 WriteProperty,一次性把屬性名和值都寫入了。因此我們可以新建立一個名為 Extensions.cs 的腳本專門去存儲這些針對JsonWriter的拓展方法,代碼如下:

 1 namespace LitJson.Extensions {
 2 
 3     /// <summary>
 4     /// 拓展方法
 5     /// </summary>
 6     public static class Extensions {
 7 
 8         public static void WriteProperty(this JsonWriter w,string name,long value){
 9             w.WritePropertyName(name);
10             w.Write(value);
11         }
12         
13         public static void WriteProperty(this JsonWriter w,string name,string value){
14             w.WritePropertyName(name);
15             w.Write(value);
16         }
17         
18         public static void WriteProperty(this JsonWriter w,string name,bool value){
19             w.WritePropertyName(name);
20             w.Write(value);
21         }
22         
23         public static void WriteProperty(this JsonWriter w,string name,double value){
24             w.WritePropertyName(name);
25             w.Write(value);
26         }
27 
28     }
29 }
View Code

  然后再來看看對於稍微復雜一些的 Rect 類型如何做支持,還是先看一下Unity中關於 Rect 類型的定義:

   在這里,我們還是關注 Rect的構造器,里面需要傳入 x 、y、width、height 這四個變量就可以了,因此參照上面的的步驟,我們依次將這幾個成員變量寫入一個Json的Object中就可以了。為了更加規整和結構分明,馬三把這些對拓展類型支持的代碼都統一放在了一個名為 UnityTypeBindings 的類中,為了能夠實現在Unity啟動時就注冊相關導出規則,我們可以在靜態構造器中調用一下 Register 函數完成注冊,並且在類前面打上 [UnityEditor.InitializeOnLoad] 的標簽,這樣就會在Unity引擎加載的時候自動把類型注冊了。最后上一下拓展導出規則這部分的代碼:

  1 using UnityEngine;
  2 using System;
  3 using System.Collections;
  4 
  5 using LitJson.Extensions;
  6 
  7 namespace LitJson
  8 {
  9 
 10 #if UNITY_EDITOR
 11     [UnityEditor.InitializeOnLoad]
 12 #endif
 13     /// <summary>
 14     /// Unity內建類型拓展
 15     /// </summary>
 16     public static class UnityTypeBindings
 17     {
 18 
 19         static bool registerd;
 20 
 21         static UnityTypeBindings()
 22         {
 23             Register();
 24         }
 25 
 26         public static void Register()
 27         {
 28 
 29             if (registerd) return;
 30             registerd = true;
 31 
 32 
 33             // 注冊Type類型的Exporter
 34             JsonMapper.RegisterExporter<Type>((v, w) =>
 35             {
 36                 w.Write(v.FullName);
 37             });
 38 
 39             JsonMapper.RegisterImporter<string, Type>((s) =>
 40             {
 41                 return Type.GetType(s);
 42             });
 43 
 44             // 注冊Vector2類型的Exporter
 45             Action<Vector2, JsonWriter> writeVector2 = (v, w) =>
 46             {
 47                 w.WriteObjectStart();
 48                 w.WriteProperty("x", v.x);
 49                 w.WriteProperty("y", v.y);
 50                 w.WriteObjectEnd();
 51             };
 52 
 53             JsonMapper.RegisterExporter<Vector2>((v, w) =>
 54             {
 55                 writeVector2(v, w);
 56             });
 57 
 58             // 注冊Vector3類型的Exporter
 59             Action<Vector3, JsonWriter> writeVector3 = (v, w) =>
 60             {
 61                 w.WriteObjectStart();
 62                 w.WriteProperty("x", v.x);
 63                 w.WriteProperty("y", v.y);
 64                 w.WriteProperty("z", v.z);
 65                 w.WriteObjectEnd();
 66             };
 67 
 68             JsonMapper.RegisterExporter<Vector3>((v, w) =>
 69             {
 70                 writeVector3(v, w);
 71             });
 72 
 73             // 注冊Vector4類型的Exporter
 74             JsonMapper.RegisterExporter<Vector4>((v, w) =>
 75             {
 76                 w.WriteObjectStart();
 77                 w.WriteProperty("x", v.x);
 78                 w.WriteProperty("y", v.y);
 79                 w.WriteProperty("z", v.z);
 80                 w.WriteProperty("w", v.w);
 81                 w.WriteObjectEnd();
 82             });
 83 
 84             // 注冊Quaternion類型的Exporter
 85             JsonMapper.RegisterExporter<Quaternion>((v, w) =>
 86             {
 87                 w.WriteObjectStart();
 88                 w.WriteProperty("x", v.x);
 89                 w.WriteProperty("y", v.y);
 90                 w.WriteProperty("z", v.z);
 91                 w.WriteProperty("w", v.w);
 92                 w.WriteObjectEnd();
 93             });
 94 
 95             // 注冊Color類型的Exporter
 96             JsonMapper.RegisterExporter<Color>((v, w) =>
 97             {
 98                 w.WriteObjectStart();
 99                 w.WriteProperty("r", v.r);
100                 w.WriteProperty("g", v.g);
101                 w.WriteProperty("b", v.b);
102                 w.WriteProperty("a", v.a);
103                 w.WriteObjectEnd();
104             });
105 
106             // 注冊Color32類型的Exporter
107             JsonMapper.RegisterExporter<Color32>((v, w) =>
108             {
109                 w.WriteObjectStart();
110                 w.WriteProperty("r", v.r);
111                 w.WriteProperty("g", v.g);
112                 w.WriteProperty("b", v.b);
113                 w.WriteProperty("a", v.a);
114                 w.WriteObjectEnd();
115             });
116 
117             // 注冊Bounds類型的Exporter
118             JsonMapper.RegisterExporter<Bounds>((v, w) =>
119             {
120                 w.WriteObjectStart();
121 
122                 w.WritePropertyName("center");
123                 writeVector3(v.center, w);
124 
125                 w.WritePropertyName("size");
126                 writeVector3(v.size, w);
127 
128                 w.WriteObjectEnd();
129             });
130 
131             // 注冊Rect類型的Exporter
132             JsonMapper.RegisterExporter<Rect>((v, w) =>
133             {
134                 w.WriteObjectStart();
135                 w.WriteProperty("x", v.x);
136                 w.WriteProperty("y", v.y);
137                 w.WriteProperty("width", v.width);
138                 w.WriteProperty("height", v.height);
139                 w.WriteObjectEnd();
140             });
141 
142             // 注冊RectOffset類型的Exporter
143             JsonMapper.RegisterExporter<RectOffset>((v, w) =>
144             {
145                 w.WriteObjectStart();
146                 w.WriteProperty("top", v.top);
147                 w.WriteProperty("left", v.left);
148                 w.WriteProperty("bottom", v.bottom);
149                 w.WriteProperty("right", v.right);
150                 w.WriteObjectEnd();
151             });
152 
153         }
154 
155     }
156 }
View Code

  最后總結一下如何針對特殊類型自定義導出規則:首先觀察其構造器需要哪些變量,將這些變量以鍵值的形式寫入到一個JsonObject中差不多就可以了,如果不放心有一些特殊的成員變量,也可以寫進去。

4.支持 JsonIgnore 跳過序列化Attribute

  在序列化一個對象的過程中,我們有時希望某些字段是不被導出的。在Newtonsoft.Json庫里面就有一個 JsonIgnore 的 Attribute,可以跳過序列化,比較可惜的是LitJson中沒有這個Attribute,不過在我們理解過LitJson的源碼以后,加一個這個Attribute其實也並不是什么難事。還記得上文中我們有講過在WriteValue這個函數中,LitJson是如何處理導出一個類的所有信息的嗎?它會拿到這個類的所有字段和屬性,然后遞歸地執行WriteValue函數。因此我們可以在這里做些手腳,在對每個字段和屬性執行WriteValue前,不妨加入一步檢測。使用 GetCustomAttributes(typeof(JsonIgnore), true); 拿到這個字段上有所有的JsonIngore Attribute,返回的參數是一個 array,我們只要判斷這個 array的長度是不是大於0就可以知道這個字段有沒有被 JsonIgnore 標記了,然后只要有標記過的字段 就執行continue跳過就可以了。現在讓我們看一下這部分代碼吧:

 1 foreach (PropertyMetadata p_data in props)
 2             {
 3                 var skipAttributesList = p_data.Info.GetCustomAttributes(typeof(JsonIgnore), true);
 4                 var skipAttributes = skipAttributesList as ICollection<Attribute>;
 5                 if (skipAttributes.Count > 0)
 6                 {
 7                     continue;
 8                 }
 9                 if (p_data.IsField)
10                 {
11                     writer.WritePropertyName(p_data.Info.Name);
12                     WriteValue(((FieldInfo)p_data.Info).GetValue(obj),
13                                 writer, writer_is_private, depth + 1);
14                 }
15 ...
16 }
View Code
1     /// <summary>
2     /// 跳過序列化的標簽
3     /// </summary>
4     [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
5     public sealed class JsonIgnore : Attribute
6     {
7 
8     }
View Code

5.檢測改造的成果

  好了現在我們可以檢測一下我們的成果了,測試一下到底好不好用。可以在Unity引擎里面隨便創建一個ScirptableObject腳本,里面填上一些我們改造后支持的特性,然后生成一個對應的 .asset 對象。然后再編寫一個簡單的 Converter 轉換器實現兩者之間的轉換,最后觀察一下數據對不對就可以了。下面看一下具體的代碼和效果。

  TestScriptableObj.cs:

 1 using System.Collections;
 2 using System.Collections.Generic;
 3 using UnityEngine;
 4 using LitJson.Extensions;
 5 
 6 [CreateAssetMenu(fileName = "TestScriptableObj",menuName = "ColaFramework/TestScriptableObj")]
 7 public class TestScriptableObj:ScriptableObject
 8 {
 9     public int num1;
10 
11     public float num2;
12 
13     public Vector2 v2;
14 
15     public Vector3 v3;
16 
17     public Quaternion quaternion;
18 
19     public Color color1;
20 
21     public Color32 color2;
22 
23     public Bounds bounds;
24 
25     public Rect rect;
26 
27     public AnimationCurve animationCurve;
28 
29     [JsonIgnore]
30     public string SerializeField;
31 }
View Code

  Converter.cs:

 1 using System.Collections;
 2 using System.Collections.Generic;
 3 using UnityEngine;
 4 using UnityEditor;
 5 using LitJson;
 6 using System.IO;
 7 
 8 public class Converter 
 9 {
10     private static readonly string JsonPath = "Assets/TrasnferData/TestScriptableObj.json";
11     private static readonly string ScriptableObjectPath = "Assets/TestScriptableObj.asset";
12     private static readonly string TransScritptableObjectPath = "Assets/TrasnferData/TestScriptableObj.asset";
13 
14     [MenuItem("ColaFramework/序列化為Json")]
15     public static void Trans2Json()
16     {
17         var asset = AssetDatabase.LoadAssetAtPath<TestScriptableObj>(ScriptableObjectPath);
18         var jsonContent = JsonMapper.ToJson(asset);
19         using(var stream = new StreamWriter(JsonPath))
20         {
21             stream.Write(jsonContent);
22         }
23         AssetDatabase.Refresh();
24     }
25 
26     [MenuItem("ColaFramework/反序列化為ScriptableObject")]
27     public static void Trans2ScriptableObject()
28     {
29         if (!File.Exists(JsonPath)) return;
30         using(var stream = new StreamReader(JsonPath))
31         {
32             var jsonStr = stream.ReadToEnd();
33             var striptableObj = JsonMapper.ToObject<TestScriptableObj>(jsonStr);
34             AssetDatabase.CreateAsset(striptableObj, TransScritptableObjectPath);
35             AssetDatabase.Refresh();
36         }
37     }
38 }
View Code

  觀察的結果:

 

  可以看到,結果符合我們的預期,也是比較正確的。導出來的Json文件還可以反向轉化為ScirptableObject,證明我們的序列化和反序列化都OK了。這里還有一個小知識點,原版的LitJson在輸出Json文件的時候,並不會像馬三截圖中的那樣是經過格式化的Json,看起來比較舒服。原版的LitJson會直接把Json內容全部打印在一行上,非常難以觀察。要改這個也容易,JsonWriter這個類中有個 pretty_print 字段,它的默認值是 false,我們只要將它在Init函數中置為 true,就可以實現LitJson以格式化的形式輸出Json內容啦!

四、源碼托管地址與使用方法

  本篇博客中的改造版LitJson源碼托管在Github上:https://github.com/XINCGer/LitJson4Unity 。使用方法很簡單,直接把Plugins/LitJson目錄下所有的cs腳本放到你的工程中即可。

五、總結

  在本篇博客中,馬三跟大家一起針對原生的LitJson進行了一些改造和拓展,以便讓它支持更多的特性,更加易用。雖然LitJson在對Unity類型的支持上稍微有些不盡人意,但是不可否認的是它仍然是一個優秀的Json庫,其源碼也是有一定的質量,通過閱讀源碼,對源碼進行分析和思考,也可以提升我們的編碼水平、深化編程思想。針對LitJson的改造方式可能千差萬別,也一定會有比馬三更好的思路出現,還是那句話馬三在這里只是拋磚引玉,以啟發大家更多的思路,如果能夠提供給大家一些幫助那就不勝榮幸了。殊途同歸,針對源碼的改造或者優化無非是讓程序更加健壯、易用,節省我們的時間,提升我們的生活質感。

  最后馬三還給大家留了一個小小的問題:在上面的改造過程中,我們只針對導出部分編寫並注冊了相關exporter規則,並沒有又去編寫一份importer規則,為什么就能夠同時實現對這些類型的導出和導入,即序列化和反序列化呢?這個問題就留給大家去思考了~

 

 

 

 

 

如果覺得本篇博客對您有幫助,可以掃碼小小地鼓勵下馬三,馬三會寫出更多的好文章,支持微信和支付寶喲!

       

 

作者:馬三小伙兒
出處:https://www.cnblogs.com/msxh/p/12541159.html
請尊重別人的勞動成果,讓分享成為一種美德,歡迎轉載。另外,文章在表述和代碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM