【Unity遊戲開發】跟着馬三一起魔改LitJson
- 2020 年 3 月 30 日
- 筆記
一、引子
在遊戲開發中,我們少不了和數據打交道,數據的存儲格式可謂是百花齊放,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
請尊重別人的勞動成果,讓分享成為一種美德,歡迎轉載。另外,文章在表述和代碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!