【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 }
可看到在这个函数里面将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 }
首先做了一个解析层数或者叫解析深度的判断,这个是为了防止相互依赖引用的情况下导致解析进入死循环或者爆栈。然后在这里处理了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; }
首先外部会传来一个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 }
然后再来看看对于稍微复杂一些的 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 }
最后总结一下如何针对特殊类型自定义导出规则:首先观察其构造器需要哪些变量,将这些变量以键值的形式写入到一个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 }
1 /// <summary> 2 /// 跳过序列化的标签 3 /// </summary> 4 [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] 5 public sealed class JsonIgnore : Attribute 6 { 7 8 }
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 }
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 }
观察的结果:
可以看到,结果符合我们的预期,也是比较正确的。导出来的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
请尊重别人的劳动成果,让分享成为一种美德,欢迎转载。另外,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!