一、引子

  在游戏开发中,我们少不了和数据打交道,数据的存储格式可谓是百花齐放,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 这个函数的代码,以下是该函数的一些代码片段:

 private static void RegisterBaseExporters()
{
base_exporters_table[typeof(byte)] =
delegate (object obj, JsonWriter writer)
{
writer.Write(Convert.ToInt32((byte)obj));
}; base_exporters_table[typeof(char)] =
delegate (object obj, JsonWriter writer)
{
writer.Write(Convert.ToString((char)obj));
};
...
}

  可看到在这个函数里面将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函数中的一些关键性代码:

 private static void WriteValue(object obj, JsonWriter writer,
bool writer_is_private,
int depth)
{
if (depth > max_nesting_depth)
throw new JsonException(
String.Format("Max allowed object depth reached while " +
"trying to export from type {0}",
obj.GetType())); if (obj == null)
{
writer.Write(null);
return;
} if (obj is IJsonWrapper)
{
if (writer_is_private)
writer.TextWriter.Write(((IJsonWrapper)obj).ToJson());
else
((IJsonWrapper)obj).ToJson(writer); return;
} if (obj is String)
{
writer.Write((string)obj);
return;
}
... if (obj is Array)
{
writer.WriteArrayStart(); foreach (object elem in (Array)obj)
WriteValue(elem, writer, writer_is_private, depth + ); writer.WriteArrayEnd(); return;
} if (obj is IList)
{
writer.WriteArrayStart();
foreach (object elem in (IList)obj)
WriteValue(elem, writer, writer_is_private, depth + );
writer.WriteArrayEnd(); return;
} if (obj is IDictionary dictionary) {
writer.WriteObjectStart();
foreach (DictionaryEntry entry in dictionary)
{
var propertyName = entry.Key is string ? (entry.Key as string) : Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
writer.WritePropertyName(propertyName);
WriteValue(entry.Value, writer, writer_is_private,
depth + );
}
writer.WriteObjectEnd(); return;
} Type obj_type = obj.GetType(); // See if there's a custom exporter for the object
if (custom_exporters_table.ContainsKey(obj_type))
{
ExporterFunc exporter = custom_exporters_table[obj_type];
exporter(obj, writer); return;
} // If not, maybe there's a base exporter
if (base_exporters_table.ContainsKey(obj_type))
{
ExporterFunc exporter = base_exporters_table[obj_type];
exporter(obj, writer); return;
} // Last option, let's see if it's an enum
if (obj is Enum)
{
Type e_type = Enum.GetUnderlyingType(obj_type); if (e_type == typeof(long)
|| e_type == typeof(uint)
|| e_type == typeof(ulong))
writer.Write((ulong)obj);
else
writer.Write((int)obj); return;
} // Okay, so it looks like the input should be exported as an
// object
AddTypeProperties(obj_type);
IList<PropertyMetadata> props = type_properties[obj_type]; writer.WriteObjectStart();
foreach (PropertyMetadata p_data in props)
{
if (p_data.IsField)
{
writer.WritePropertyName(p_data.Info.Name);
WriteValue(((FieldInfo)p_data.Info).GetValue(obj),
writer, writer_is_private, depth + );
}
else
{
PropertyInfo p_info = (PropertyInfo)p_data.Info; if (p_info.CanRead)
{
writer.WritePropertyName(p_data.Info.Name);
WriteValue(p_info.GetValue(obj, null),
writer, writer_is_private, depth + );
}
}
}
writer.WriteObjectEnd();
}

  首先做了一个解析层数或者叫解析深度的判断,这个是为了防止相互依赖引用的情况下导致解析进入死循环或者爆栈。然后在这里处理了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 = ; 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的拓展方法,代码如下:

 namespace LitJson.Extensions {

     /// <summary>
/// 拓展方法
/// </summary>
public static class Extensions { public static void WriteProperty(this JsonWriter w,string name,long value){
w.WritePropertyName(name);
w.Write(value);
} public static void WriteProperty(this JsonWriter w,string name,string value){
w.WritePropertyName(name);
w.Write(value);
} public static void WriteProperty(this JsonWriter w,string name,bool value){
w.WritePropertyName(name);
w.Write(value);
} public static void WriteProperty(this JsonWriter w,string name,double value){
w.WritePropertyName(name);
w.Write(value);
} }
}

  然后再来看看对于稍微复杂一些的 Rect 类型如何做支持,还是先看一下Unity中关于 Rect 类型的定义:

  在这里,我们还是关注 Rect的构造器,里面需要传入 x 、y、width、height 这四个变量就可以了,因此参照上面的的步骤,我们依次将这几个成员变量写入一个Json的Object中就可以了。为了更加规整和结构分明,马三把这些对拓展类型支持的代码都统一放在了一个名为 UnityTypeBindings 的类中,为了能够实现在Unity启动时就注册相关导出规则,我们可以在静态构造器中调用一下 Register 函数完成注册,并且在类前面打上 [UnityEditor.InitializeOnLoad] 的标签,这样就会在Unity引擎加载的时候自动把类型注册了。最后上一下拓展导出规则这部分的代码:

 using UnityEngine;
using System;
using System.Collections; using LitJson.Extensions; namespace LitJson
{ #if UNITY_EDITOR
[UnityEditor.InitializeOnLoad]
#endif
/// <summary>
/// Unity内建类型拓展
/// </summary>
public static class UnityTypeBindings
{ static bool registerd; static UnityTypeBindings()
{
Register();
} public static void Register()
{ if (registerd) return;
registerd = true; // 注册Type类型的Exporter
JsonMapper.RegisterExporter<Type>((v, w) =>
{
w.Write(v.FullName);
}); JsonMapper.RegisterImporter<string, Type>((s) =>
{
return Type.GetType(s);
}); // 注册Vector2类型的Exporter
Action<Vector2, JsonWriter> writeVector2 = (v, w) =>
{
w.WriteObjectStart();
w.WriteProperty("x", v.x);
w.WriteProperty("y", v.y);
w.WriteObjectEnd();
}; JsonMapper.RegisterExporter<Vector2>((v, w) =>
{
writeVector2(v, w);
}); // 注册Vector3类型的Exporter
Action<Vector3, JsonWriter> writeVector3 = (v, w) =>
{
w.WriteObjectStart();
w.WriteProperty("x", v.x);
w.WriteProperty("y", v.y);
w.WriteProperty("z", v.z);
w.WriteObjectEnd();
}; JsonMapper.RegisterExporter<Vector3>((v, w) =>
{
writeVector3(v, w);
}); // 注册Vector4类型的Exporter
JsonMapper.RegisterExporter<Vector4>((v, w) =>
{
w.WriteObjectStart();
w.WriteProperty("x", v.x);
w.WriteProperty("y", v.y);
w.WriteProperty("z", v.z);
w.WriteProperty("w", v.w);
w.WriteObjectEnd();
}); // 注册Quaternion类型的Exporter
JsonMapper.RegisterExporter<Quaternion>((v, w) =>
{
w.WriteObjectStart();
w.WriteProperty("x", v.x);
w.WriteProperty("y", v.y);
w.WriteProperty("z", v.z);
w.WriteProperty("w", v.w);
w.WriteObjectEnd();
}); // 注册Color类型的Exporter
JsonMapper.RegisterExporter<Color>((v, w) =>
{
w.WriteObjectStart();
w.WriteProperty("r", v.r);
w.WriteProperty("g", v.g);
w.WriteProperty("b", v.b);
w.WriteProperty("a", v.a);
w.WriteObjectEnd();
}); // 注册Color32类型的Exporter
JsonMapper.RegisterExporter<Color32>((v, w) =>
{
w.WriteObjectStart();
w.WriteProperty("r", v.r);
w.WriteProperty("g", v.g);
w.WriteProperty("b", v.b);
w.WriteProperty("a", v.a);
w.WriteObjectEnd();
}); // 注册Bounds类型的Exporter
JsonMapper.RegisterExporter<Bounds>((v, w) =>
{
w.WriteObjectStart(); w.WritePropertyName("center");
writeVector3(v.center, w); w.WritePropertyName("size");
writeVector3(v.size, w); w.WriteObjectEnd();
}); // 注册Rect类型的Exporter
JsonMapper.RegisterExporter<Rect>((v, w) =>
{
w.WriteObjectStart();
w.WriteProperty("x", v.x);
w.WriteProperty("y", v.y);
w.WriteProperty("width", v.width);
w.WriteProperty("height", v.height);
w.WriteObjectEnd();
}); // 注册RectOffset类型的Exporter
JsonMapper.RegisterExporter<RectOffset>((v, w) =>
{
w.WriteObjectStart();
w.WriteProperty("top", v.top);
w.WriteProperty("left", v.left);
w.WriteProperty("bottom", v.bottom);
w.WriteProperty("right", v.right);
w.WriteObjectEnd();
}); } }
}

  最后总结一下如何针对特殊类型自定义导出规则:首先观察其构造器需要哪些变量,将这些变量以键值的形式写入到一个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跳过就可以了。现在让我们看一下这部分代码吧:

 foreach (PropertyMetadata p_data in props)
{
var skipAttributesList = p_data.Info.GetCustomAttributes(typeof(JsonIgnore), true);
var skipAttributes = skipAttributesList as ICollection<Attribute>;
if (skipAttributes.Count > )
{
continue;
}
if (p_data.IsField)
{
writer.WritePropertyName(p_data.Info.Name);
WriteValue(((FieldInfo)p_data.Info).GetValue(obj),
writer, writer_is_private, depth + );
}
...
}
     /// <summary>
/// 跳过序列化的标签
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public sealed class JsonIgnore : Attribute
{ }

5.检测改造的成果

  好了现在我们可以检测一下我们的成果了,测试一下到底好不好用。可以在Unity引擎里面随便创建一个ScirptableObject脚本,里面填上一些我们改造后支持的特性,然后生成一个对应的 .asset 对象。然后再编写一个简单的 Converter 转换器实现两者之间的转换,最后观察一下数据对不对就可以了。下面看一下具体的代码和效果。

  TestScriptableObj.cs:

 using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LitJson.Extensions; [CreateAssetMenu(fileName = "TestScriptableObj",menuName = "ColaFramework/TestScriptableObj")]
public class TestScriptableObj:ScriptableObject
{
public int num1; public float num2; public Vector2 v2; public Vector3 v3; public Quaternion quaternion; public Color color1; public Color32 color2; public Bounds bounds; public Rect rect; public AnimationCurve animationCurve; [JsonIgnore]
public string SerializeField;
}

  Converter.cs:

 using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using LitJson;
using System.IO; public class Converter
{
private static readonly string JsonPath = "Assets/TrasnferData/TestScriptableObj.json";
private static readonly string ScriptableObjectPath = "Assets/TestScriptableObj.asset";
private static readonly string TransScritptableObjectPath = "Assets/TrasnferData/TestScriptableObj.asset"; [MenuItem("ColaFramework/序列化为Json")]
public static void Trans2Json()
{
var asset = AssetDatabase.LoadAssetAtPath<TestScriptableObj>(ScriptableObjectPath);
var jsonContent = JsonMapper.ToJson(asset);
using(var stream = new StreamWriter(JsonPath))
{
stream.Write(jsonContent);
}
AssetDatabase.Refresh();
} [MenuItem("ColaFramework/反序列化为ScriptableObject")]
public static void Trans2ScriptableObject()
{
if (!File.Exists(JsonPath)) return;
using(var stream = new StreamReader(JsonPath))
{
var jsonStr = stream.ReadToEnd();
var striptableObj = JsonMapper.ToObject<TestScriptableObj>(jsonStr);
AssetDatabase.CreateAsset(striptableObj, TransScritptableObjectPath);
AssetDatabase.Refresh();
}
}
}

  观察的结果:

 

  可以看到,结果符合我们的预期,也是比较正确的。导出来的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
请尊重别人的劳动成果,让分享成为一种美德,欢迎转载。另外,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!

【Unity游戏开发】跟着马三一起魔改LitJson的更多相关文章

  1. C# Unity游戏开发——Excel中的数据是如何到游戏中的 (三)

    本帖是延续的:C# Unity游戏开发——Excel中的数据是如何到游戏中的 (二) 前几天有点事情所以没有继续更新,今天我们接着说.上个帖子中我们看到已经把Excel数据生成了.bin的文件,不过其 ...

  2. 【Unity游戏开发】记一次解决 LuaFunction has been disposed 的bug的过程

    一.引子 RT,本篇博客记录的是马三的一次解决 LuaFunction has been disposed 的bug的全过程,事情还要从马三的自研框架 ColaFrameWork 说起.最近,马三在业 ...

  3. 【Unity游戏开发】浅谈Lua和C#中的闭包

    一.前言 目前在Unity游戏开发中,比较流行的两种语言就是Lua和C#.通常的做法是:C#做些核心的功能和接口供Lua调用,Lua主要做些UI模块和一些业务逻辑.这样既能在保持一定的游戏运行效率的同 ...

  4. 【Unity游戏开发】用C#和Lua实现Unity中的事件分发机制EventDispatcher

    一.简介 最近马三换了一家大公司工作,公司制度规范了一些,因此平时的业余时间多了不少.但是人却懒了下来,最近这一个月都没怎么研究新技术,博客写得也是拖拖拉拉,周六周天就躺尸在家看帖子.看小说,要么就是 ...

  5. 【Unity游戏开发】不接SDK也能在游戏内拉起加QQ群操作?

    一.引子 一般在游戏进行对外测试的时候都会有一个玩家QQ群,方便玩家反馈问题.交流游戏心得等.那么为了增加玩家加QQ群的欲望,可能会在游戏里面设计一个小功能,点击一下可以直接拉起手Q加群的操作,加了Q ...

  6. 关于Unity游戏开发方向找工作方面的一些个人看法

     这是个老生常谈,却又是谁绕不过去的话题,而对于每个人来说,所遇到的情况又不尽相同,别人的求职方式和路线不一定适合你,即使是背景很相似的两个人,有时候机遇也很重要. 我本人的工作经验只有一年,就业方式 ...

  7. 《MFC游戏开发》笔记三 游戏贴图与透明特效的实现

    本系列文章由七十一雾央编写,转载请注明出处. 313239 作者:七十一雾央 新浪微博:http://weibo.com/1689160943/profile?rightmod=1&wvr=5 ...

  8. C# Unity游戏开发——Excel中的数据是如何到游戏中的 (四)2018.4.3更新

    本帖是延续的:C# Unity游戏开发--Excel中的数据是如何到游戏中的 (三) 最近项目不算太忙,终于有时间更新博客了.关于数据处理这个主题前面的(一)(二)(三)基本上算是一个完整的静态数据处 ...

  9. 2017年Unity游戏开发视频教程(入门到精通)

    本文是我发布的一个Unity游戏开发的学习目录,以后我会持续发布一系列的游戏开发教程,都会更新在这个页面上,适合人群有下面的几种: 想要做独立游戏的人 想要找游戏开发相关工作的人 对游戏开发感兴趣的人 ...

随机推荐

  1. 剑指offer-18-2. 删除链表中重复的结点

    剑指offer-18-2. 删除链表中重复的结点 链表 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针. 例如,链表1->2->3-> ...

  2. 创建 GPG 证书

    一.什么是 GPG 以下引自维基百科: GNU Privacy Guard(GnuPG或GPG)是一种加密软件,它是PGP加密软件的满足GPL的替代物.GnuPG依照由IETF订定的OpenPGP技术 ...

  3. VR、网剧如何成为民间骗子中的朝阳产业

    ​ 互联网的发达,让大众有了了解世界最好的传播工具.但与此同时,大量信息潮的涌来,让人们难以分辨其中的真假.于是,原本靠大力丸.保健药.假公章等行骗的骗子们开始转变方向,利用信息不对称性,大肆捏造与热 ...

  4. Java入门教程十三(多线程)

    线程的概念 单纯种以一个任务完成以后再进行下一个任务的模式进行,这样下一个任务的开始必须等待前一个任务的结束,只有一个任务完成后才能进行下一个任务.Java 语言提供了并发机制,允许开发人员在程序中执 ...

  5. 50-Python2和3字符编码的区别

    目录 Python2和3字符编码的区别 python2 python3 Python2和3字符编码的区别 区别点 python2 python3 print 是一个语法结构 是一个函数,print(' ...

  6. JMeter-完成批量的接口测试

    前言 当我们在工作中进行接口测试时,项目的接口肯定不止一个,而是很多很多,而且每个接口都需要进行正确参数,错误参数,参数为空,特殊字符等方式来测试接口是否能够正确返回所需的响应值. 今天,我们来一起学 ...

  7. 7-49 求前n项的阶乘之和 (15 分)

    从键盘输入一个整数n,求前n项的阶乘之和,1+2!+3!+...+n!的和 输入格式: 输入一个大于1的整数.例如:输入20. 输出格式: 输出一个整数.例如:2561327494111820313. ...

  8. fsLayuiPlugin数据表格动态转义

    数据表格动态转义提供一种更简洁的方式,主要解决前端laytpl模板转义的问题,对于一些简单的,例如:状态展示,我们可以通过前端编写laytpl模板来处理:对于动态的数据,通过这种静态方式是没有办法处理 ...

  9. 前端面试题-HTML语义化标签

    一.HTML5语义化标签 标签 描述 <article> 页面独立的内容区域. <aside> 页面的侧边栏内容. <bdi> 允许您设置一段文本,使其脱离其父元素 ...

  10. 2020年,大厂常问iOS面试题汇总!

    Runloop & KVO runloop app如何接收到触摸事件的 为什么只有主线程的runloop是开启的 为什么只在主线程刷新UI PerformSelector和runloop的关系 ...