Unity中关于Json的那些事
前言
Json是在游戏开发过程中十分常用的数据文件类型。比如slg游戏兵种的数值设置,或是一些玩家的游戏配置等。这些都可以使用Json来实现。这篇文章就来讨论一些在Unity中关于Json的那些事,大部分是我自己使用Json的案例,也会扩展一些网上找到的方法。
环境
Windows10系统
Unity2022.3.8f1c1
JsonUtility:Unity自带的Json解析
JsonUtility是Unity官方提供的一个用于处理Json数据的类。(文档连接 )它本身功能是算简单的,它一共就三个API且所支持的数据结构也较为简单。但是有时候我不想去安装其他Json插件的时候就会使用它。Unity官方也有给出其使用的例子和说明:链接 。下面我也会给出使用它的一些例子。
JsonUtility.ToJson
在Unity的官方文档 中有对其更加详细的描述,我这里挑一些我觉得重要的地方来说明。其作用的对象必须是
MonoBehaviour、ScriptableObject 或应用了 Serializable
属性的普通类/结构。其也不支持其他引擎类型。要包含的字段的类型必须受序列化器支持;不受支持的字段以及私有字段、静态字段和应用了
NonSerialized
属性的字段会被忽略。最好不要对原始类型进行这个方法的操作,因为结果可能不会符合你的预期。如果你有这个需要那么也要封装到类中并且满足条件。以下是我写的转json的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 using System.Collections.Generic;using UnityEngine;namespace JsonTest { public enum JsonEnumTest { None, Json, Test, End } public class UnityToJson : MonoBehaviour { public string curName; public int intValue; public float floatValue; public bool boolValue; public double doubleValue; public List<string > strList; public string [] strArray; public Dictionary<string , string > strDictionary; public JsonEnumTest enumTest; private void Awake () { curName = "ToJson测试" ; intValue = 0 ; floatValue = 1.0f ; boolValue = false ; doubleValue = 2.0f ; strArray = new string [10 ]; strDictionary = new Dictionary<string , string >(); strList = new List<string >(); for (int i = 0 ; i < 10 ; ++i) { int j = i; strList.Add(j.ToString()); j *= 10 ; strArray[i] = j.ToString(); j *= 10 ; strDictionary.Add((j - 1 ).ToString(), j.ToString()); } } private void Start () { print(JsonUtility.ToJson(this , false )); print(JsonUtility.ToJson(this , true )); } } }
其结果如下:
1 { "curName" : "ToJson测试" , "intValue" : 0 , "floatValue" : 1.0 , "boolValue" : false , "doubleValue" : 2.0 , "strList" : [ "0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" ] , "strArray" : [ "0" , "10" , "20" , "30" , "40" , "50" , "60" , "70" , "80" , "90" ] , "enumTest" : 0 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 { "curName" : "ToJson测试" , "intValue" : 0 , "floatValue" : 1.0 , "boolValue" : false , "doubleValue" : 2.0 , "strList" : [ "0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" ] , "strArray" : [ "0" , "10" , "20" , "30" , "40" , "50" , "60" , "70" , "80" , "90" ] , "enumTest" : 0 }
观察结果我们可以发现JsonUtility.ToJson可以输出两种格式的Json。本质上他们是一样的,只是不同的人喜欢不同的格式。这个由其第二个参数来控制。除此之外,我们还可以发现结果中少了strDictionary。因为Unity自带的Json转换是不支持字典的。如果你要支持字典,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 using System.Collections.Generic;using UnityEngine;namespace JsonTest { public class CommonClass { public string name; public int intValue; public float floatValue; public bool boolValue; public double doubleValue; public List<string > strList; public string [] strArray; public Dictionary<string , string > strDictionary; } public enum JsonEnumTest { None, Json, Test, End } public class UnityToJson : MonoBehaviour ,ISerializationCallbackReceiver { public string curName; public int intValue; public float floatValue; public bool boolValue; public double doubleValue; public List<string > strList; public string [] strArray; public Dictionary<string , string > strDictionary; public JsonEnumTest enumTest; [HideInInspector ] [SerializeField ] private List<string > keyStr; [HideInInspector ] [SerializeField ] private List<string > valueStr; private void Awake () { curName = "ToJson测试" ; intValue = 0 ; floatValue = 1.0f ; boolValue = false ; doubleValue = 2.0f ; strArray = new string [10 ]; strDictionary = new Dictionary<string , string >(); strList = new List<string >(); for (int i = 0 ; i < 10 ; ++i) { int j = i; strList.Add(j.ToString()); j *= 10 ; strArray[i] = j.ToString(); j *= 10 ; strDictionary.Add((j - 1 ).ToString(), j.ToString()); } } private void Start () { print(JsonUtility.ToJson(this , false )); print(JsonUtility.ToJson(this , true )); } public void OnAfterDeserialize () {} public void OnBeforeSerialize () { if (strDictionary != null && strDictionary.Count > 0 ) { keyStr = new List<string >(); valueStr = new List<string >(); foreach (var i in strDictionary.Keys) { keyStr.Add(i); valueStr.Add(strDictionary[i]); } } } } }
我们让UnityToJson实现ISerializationCallbackReceiver接口。其有两个函数OnBeforeSerialize(在序列化前)和OnAfterDeserialize(序列化后)。对于ToJson而言,我们只要实现OnBeforeSerialize就可以了。这里我分别使用了两个List去存储字典的Key和Value。结果如下(这里为了节省篇幅,我就只放了一个格式。大家要是觉得这个格式难看,可以去网上找一个json在线解析的网站转换一下格式。)
1 { "curName" : "ToJson测试" , "intValue" : 0 , "floatValue" : 1.0 , "boolValue" : false , "doubleValue" : 2.0 , "strList" : [ "0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" ] , "strArray" : [ "0" , "10" , "20" , "30" , "40" , "50" , "60" , "70" , "80" , "90" ] , "enumTest" : 0 , "keyStr" : [ "-1" , "99" , "199" , "299" , "399" , "499" , "599" , "699" , "799" , "899" ] , "valueStr" : [ "0" , "100" , "200" , "300" , "400" , "500" , "600" , "700" , "800" , "900" ] }
后面我们如果要读这个数据则要做一些特殊操作才可以将这两个List转换为字典。这个转换留在后文说明。
JsonUtility.ToJson
本身的功能并不强大。它能满足的开发场景很小,因为稍微复杂一点的数据结构它就不能解析。比如List<List>这样的数据结构。而像List<List>也算是常见的数据类型了。如果你要支持的话,则也要像字典一样通过OnBeforeSerialize函数来进行ToJson的转换。
JsonUtility.FromJson和JsonUtility.FromJsonOverwrite
JsonUtility.FromJson和JsonUtility.FromJsonOverwrite,它们都会将符合json格式的字符串转为对象的数据,且都不支持其他引擎类型。区别是FromJson是创建一个新的对象,而FromJsonOverwrite只是覆盖现有对象的数据。除此之外FromJson只支持普通类和结构,不支持派生自
UnityEngine.Object 的类(如 MonoBehaviour 或
ScriptableObject)。FromJsonOverwrite则支持任何普通类或结构,以及派生自
MonoBehaviour 或 ScriptableObject 的类。下面我举一些例子来说明。FromJson文档连接 ,FromJsonOverwrite文档连接
这里我们创建一个普通类和一个继承MonoBehaviour的类。这里为了方便能复用上文的json数据,我这里将其数据结构和上面的类一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 using System.Collections.Generic;using UnityEngine;namespace JsonTest { public class ToJsonClass { public string curName; public int intValue; public float floatValue; public bool boolValue; public double doubleValue; public List<string > strList; public string [] strArray; public Dictionary<string , string > strDictionary; public JsonEnumTest enumTest; } public class ToJsonMono : MonoBehaviour { public string curName; public int intValue; public float floatValue; public bool boolValue; public double doubleValue; public List<string > strList; public string [] strArray; public Dictionary<string , string > strDictionary; public JsonEnumTest enumTest; } }
下面是具体的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using UnityEngine;namespace JsonTest { public class UnityJsonTo : MonoBehaviour { public string json; private void Start () { var commonClass = JsonUtility.FromJson<ToJsonClass>(json); Debug.Log("普通类当前的名字:" + commonClass.curName); var monoClass = JsonUtility.FromJson<ToJsonMono>(json); Debug.Log("继承Mono类当前的名字:" + monoClass.curName); } } }
PS:大家可以直接拿下面的Josn去实验。
1 { "curName" : "ToJson测试" , "intValue" : 0 , "floatValue" : 1.0 , "boolValue" : false , "doubleValue" : 2.0 , "strList" : [ "0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" ] , "strArray" : [ "0" , "10" , "20" , "30" , "40" , "50" , "60" , "70" , "80" , "90" ] , "enumTest" : 0 , "keyStr" : [ "-1" , "99" , "199" , "299" , "399" , "499" , "599" , "699" , "799" , "899" ] , "valueStr" : [ "0" , "100" , "200" , "300" , "400" , "500" , "600" , "700" , "800" , "900" ] }
我们将其挂载在Unity的物体上后,将我们上面得到的json数据给UnityJsonTo的json,接下来点击运行。则我们会得到以下的结果:
1 2 3 4 5 6 第一个结果: 普通类当前的名字:ToJson测试 第二个结果 ArgumentException: Cannot deserialize JSON to new instances of type 'ToJsonMono.' UnityEngine.JsonUtility.FromJson (System.String json, System.Type type) (at <f726cf09079e4728a973893de74c9a35>:0) UnityEngine.JsonUtility.FromJson[T] (System.String json) (at <f726cf09079e4728a973893de74c9a35>:0)
从上面的结果我们可以确定FromJson是会创建一个新的对象,且会将数值赋值上去。但是它不可以作用于Mono类上。下面我们将代码改为下面的形式,然后再次运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using UnityEngine;namespace JsonTest { public class UnityJsonTo : MonoBehaviour { public string json; private void Start () { var commonClass = JsonUtility.FromJson<ToJsonClass>(json); Debug.Log("普通类当前的名字:" + commonClass.curName); var monoClass = this .gameObject.AddComponent<ToJsonMono>(); JsonUtility.FromJsonOverwrite(json, monoClass); Debug.Log("继承Mono类当前的名字:" + monoClass.curName); } } }
其结果如下:
1 2 3 4 第一个结果: 普通类当前的名字:ToJson测试 第二个结果 继承Mono类当前的名字:ToJson测试
FromJsonOverwrite覆盖普通类的代码,我就不再实现。其实结果大家应该也知道了吧,就算不知道大家也可以亲手实验一下。
无论是FromJsonOverwrite还是FromJson,它们都存在一个问题就是对于某些数据结构无法转换,比如字典。下面是我提供的一个有字典的Json字符串:
1 { "curName" : "ToJson测试" , "intValue" : 0 , "floatValue" : 1.0 , "boolValue" : false , "doubleValue" : 2.0 , "strList" : [ "0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" ] , "strArray" : [ "0" , "10" , "20" , "30" , "40" , "50" , "60" , "70" , "80" , "90" ] , "strDictionary" : { "-1" : "0" , "99" : "100" , "199" : "200" , "299" : "300" , "399" : "400" , "499" : "500" , "599" : "600" , "699" : "700" , "799" : "800" , "899" : "900" } , "enumTest" : 0 }
下面我以FromJson为例子,修改UnityJsonTo代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 using System.Text;using UnityEngine;namespace JsonTest { public class UnityJsonTo : MonoBehaviour { public string json; private void Start () { var commonClass = JsonUtility.FromJson<ToJsonClass>(json); var dic = commonClass.strDictionary; if (dic != null && dic.Count > 0 ) { StringBuilder stringBuilder = new StringBuilder(); foreach (var item in dic) { stringBuilder.Append("当前key值为:" + item.Key + " 当前value值为:" + item.Value + "\n" ); } Debug.Log(stringBuilder.ToString()); } else Debug.Log("字典并没有被赋值" ); } } }
其结果为:
如果我们真的需要去使用FromJson去写字典,那么我们就需要额外做一些事情。上文中,我们字典转json字符串是使用了两个List去存储字典key和value。那么我们同样使用这样的json,然后对ToJsonClass进行以下的修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class ToJsonClass : ISerializationCallbackReceiver { public string curName; public int intValue; public float floatValue; public bool boolValue; public double doubleValue; public List<string > strList; public string [] strArray; public Dictionary<string , string > strDictionary; public JsonEnumTest enumTest; [SerializeField ] private List<string > keyStr; [SerializeField ] private List<string > valueStr; public void OnAfterDeserialize () { strDictionary = new Dictionary<string , string >(); if (keyStr != null && valueStr != null && keyStr.Count == valueStr.Count && keyStr.Count > 0 ) { for (int i = 0 ;i < keyStr.Count; ++i) { strDictionary.TryAdd(keyStr[i], valueStr[i]); } } } public void OnBeforeSerialize () { } }
我们也要让ToJsonClass去继承ISerializationCallbackReceiver,因为我们要等json转换完后才得到keyStr和valueStr的值,所以我们要实现OnAfterDeserialize的方法。又因为之前我们让keyStr和valueStr一一匹配,因此我才做出这一系列的判断和字典添加。当然你也不一定要像我这样去做,你可以按照你自己项目的需求去做判断和添加的规则。然后我们使用下面Json字符串来进行执行
1 { "curName" : "ToJson测试" , "intValue" : 0 , "floatValue" : 1.0 , "boolValue" : false , "doubleValue" : 2.0 , "strList" : [ "0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" ] , "strArray" : [ "0" , "10" , "20" , "30" , "40" , "50" , "60" , "70" , "80" , "90" ] , "enumTest" : 0 , "keyStr" : [ "-1" , "99" , "199" , "299" , "399" , "499" , "599" , "699" , "799" , "899" ] , "valueStr" : [ "0" , "100" , "200" , "300" , "400" , "500" , "600" , "700" , "800" , "900" ] }
得到的结果如下:
1 2 3 4 5 6 7 8 9 10 当前key值为:-1 当前value值为:0 当前key值为:99 当前value值为:100 当前key值为:199 当前value值为:200 当前key值为:299 当前value值为:300 当前key值为:399 当前value值为:400 当前key值为:499 当前value值为:500 当前key值为:599 当前value值为:600 当前key值为:699 当前value值为:700 当前key值为:799 当前value值为:800 当前key值为:899 当前value值为:900
关于枚举
在上面的json例子中,枚举都是以数字形式被存储在json。这个自然没什么问题,我们本身也可以将枚举看做是数字。但有时候这样的形式并不方便我们去查看信息。如果你的项目决定使用JsonUtility,且对枚举有一定的要求。那么你只能自己去实现OnBeforeSerialize和OnAfterDeserialize,用一个string类型来进行枚举的转换。
总结
总体而言JsonUtility所能实现的功能还是简单了,一些复杂的数据结构就不是它所能实现的。但也正是因为它简单,所以在某些情况下它也可以发挥出不错的效果。而对于一些小项目而言,我们使用它来开发也够用了。
Newtonsoft
Newtonsoft其实是第三方插件(文档连接 )。Unity内置的包中也有一个Newtonsoft包(文档连接 )。在其文档中,它明确说明它对应第三方Newtonsoft.Json
的13.0.2版本(本人查看文档的时间:2024.1.16)。下方是其原文
1 This is a Unity package for Newtonsoft Json and corresponds to Newtonsoft.Json version 13.0.2.
大家可以在Unity的包管理中搜索Newtonsoft,然后直接安装就好了。如果你的Unity编辑器并没有这个包,那你可以选择去Newtonsoft官网下载并且将它放置于你的Unity项目下(这里要去网上找一下相关教程)。
现在(2024.1.16)第三方插件的Newtonsoft已经到达了13.0.3版本。而Unity并不提供相关的文档而是直接使用Newtonsoft官网的文档。且Newtonsoft官网的文档中并没有对个个版本进行区分,你只能找到一个文档。不过这并不影响我们使用。Newtonsoft的功能很强大,这里我并不想讲全它的功能(实际上我自己也没有用全)。我这里只会讲一些我经常用到的功能和一些遇到的坑点。
对象转json
Newtonsoft已经可以满足我们一些常见的数据结构进行转json的操作,包括字典。下面我直接给出其转json的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 using Newtonsoft.Json;using System.Collections.Generic;using UnityEngine;namespace JsonTest { public class NewtonsoftToJsonClass { public string curName; public int intValue; public float floatValue; public bool boolValue; public double doubleValue; public List<string > strList; public string [] strArray; public Dictionary<string , string > strDictionary; public JsonEnumTest enumTest; [SerializeField ] private List<string > serStrLst; private List<string > priStrLst; public void SettingPrivateStrLst (List<string > lst ) { serStrLst = new List<string >(); priStrLst = new List<string >(); foreach (string s in lst) { serStrLst.Add(s); priStrLst.Add(s); } } } public class NewtonsoftJsonSample : MonoBehaviour { private void Start () { var toJosn = new NewtonsoftToJsonClass(); toJosn.curName = "Newtonsoft测试" ; toJosn.intValue = 5 ; toJosn.doubleValue = 5 ; toJosn.floatValue = 5 ; toJosn.boolValue = true ; toJosn.enumTest = JsonEnumTest.Json; toJosn.strArray = new string [5 ]; toJosn.strList = new List<string >(); toJosn.strDictionary = new Dictionary<string , string >(); for (int i = 0 ; i < 5 ; ++i) { int j = i; toJosn.strList.Add(j.ToString()); j *= 10 ; toJosn.strArray[i] = j.ToString(); j *= 10 ; toJosn.strDictionary.Add((j - 1 ).ToString(), j.ToString()); } toJosn.SettingPrivateStrLst(toJosn.strList); Debug.Log(JsonConvert.SerializeObject(toJosn, Formatting.None)); Debug.Log(JsonConvert.SerializeObject(toJosn, Formatting.Indented)); } } }
我们将这脚本挂载到Unity中后点击运行,其结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 第一个结果: { "curName" : "Newtonsoft测试" , "intValue" : 5 , "floatValue" : 5.0 , "boolValue" : true , "doubleValue" : 5.0 , "strList" : [ "0" , "1" , "2" , "3" , "4" ] , "strArray" : [ "0" , "10" , "20" , "30" , "40" ] , "strDictionary" : { "-1" : "0" , "99" : "100" , "199" : "200" , "299" : "300" , "399" : "400" } , "enumTest" : 1 } 第二个结果: { "curName" : "Newtonsoft测试" , "intValue" : 5 , "floatValue" : 5.0 , "boolValue" : true , "doubleValue" : 5.0 , "strList" : [ "0" , "1" , "2" , "3" , "4" ] , "strArray" : [ "0" , "10" , "20" , "30" , "40" ] , "strDictionary" : { "-1" : "0" , "99" : "100" , "199" : "200" , "299" : "300" , "399" : "400" } , "enumTest" : 1 }
Newtonsoft也存在选择json格式输出的选项,且它也不支持输出用private和protected所修饰的变量。但是我们不能再使用SerializeField这样的标志来将这些变量输出为Json字符串,而是使用Newtonsoft提供的JsonRequired。我们将上面代码中的SerializeField替换为JsonRequired便可以得到下面的Json字符串(同样为了节省篇幅,我只展示一种格式):
1 { "curName" : "Newtonsoft测试" , "intValue" : 5 , "floatValue" : 5.0 , "boolValue" : true , "doubleValue" : 5.0 , "strList" : [ "0" , "1" , "2" , "3" , "4" ] , "strArray" : [ "0" , "10" , "20" , "30" , "40" ] , "strDictionary" : { "-1" : "0" , "99" : "100" , "199" : "200" , "299" : "300" , "399" : "400" } , "enumTest" : 1 , "serStrLst" : [ "0" , "1" , "2" , "3" , "4" ] }
转Json的问题
Newtonsoft在大多数情况下都可以满足我们对象转Json的需求。但是在这个过程中,它也同样会出现些问题。幸好它本身功能足够完善,只是我们需要多一些操作。
Vector3是我们在Unity中非常常用的类型。每次我们进行位置的修改,我们都需要用到这个类型。所以如果我们需要将其转为字符串,并用上面的代码则会报下面的错误:
1 JsonSerializationException: Self referencing loop detected for property 'normalized' with type 'UnityEngine.Vector3'. Path 'normalized'.
翻译:
JsonSerializationException:自引用循环检测属性'normalized'与类型'UnityEngine.Vector3'。
这个问题简单来说Vector3中存在一个与自己同类型的变量,而Newtonsoft在默认情况下是不支持这样的操作的。如果我们要Newtonsoft支持,则我们要做一些设定,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using Newtonsoft.Json;using UnityEngine;namespace JsonTest { public class NSToJsonProblem : MonoBehaviour { private void Start () { Vector3 a = Vector3.one; JsonSerializerSettings settings = new JsonSerializerSettings(); settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; Debug.Log(JsonConvert.SerializeObject(a, settings)); } } }
我们将这段代码挂载到Unity中并运行后的结果如下:
1 { "x" : 1.0 , "y" : 1.0 , "z" : 1.0 , "normalized" : { "x" : 0.577350259 , "y" : 0.577350259 , "z" : 0.577350259 , "magnitude" : 1.0 , "sqrMagnitude" : 0.99999994 } , "magnitude" : 1.73205078 , "sqrMagnitude" : 3.0 }
在上面的代码中,我们使用了JsonSerializerSettings类型,并将其中的ReferenceLoopHandling设定为Ignore。然后在转Json的时候,我们将这个设定设置到SerializeObject中。有关其更多的信息可以查看一下Newtonsoft关于其的文档 。我们使用这样的方法的确将Vector3转为了Json字符串,
如果你使用JsonUtility.ToJson,你会得到下面的结果:
1 { "x" : 1.0 , "y" : 1.0 , "z" : 1.0 }
你会发现JsonUtility的结果比Newtonsoft更加简洁。我个人是更喜欢JsonUtility的结果。那Newtonsoft能办到这样的结果吗?那自然是可以的。只是我们要手动去写转换器。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using Newtonsoft.Json;using System;using UnityEngine;namespace JsonTest { public class Vector3Convert : JsonConverter { public override bool CanConvert (Type objectType ) { return objectType == typeof (Vector3); } public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) { throw new NotImplementedException(); } public override void WriteJson (JsonWriter writer, object value , JsonSerializer serializer ) { var vt3 = (Vector3)value ; writer.WriteStartObject(); writer.WritePropertyName("x" ); writer.WriteValue(vt3.x); writer.WritePropertyName("y" ); writer.WriteValue(vt3.y); writer.WritePropertyName("z" ); writer.WriteValue(vt3.z); writer.WriteEndObject(); } } }
上面就是我写的Vector3转json的转换器。当继承于JsonConverter时,我们必须实现CanConvert,ReadJson和WriteJson这三个函数。因为我们只要转Json,所以我们只要实现CanConvert和WriteJson。CanConvert函数用来确定变量是否可以由这个转换器来进行转换,WriteJson来确定如何将这个变量转换为Json字符串。有关JsonConverter更多的信息,请看官网的文档 。这里我先将值转为我们想要的类型,然后使用JsonWriter进行Json编写。具体的Json编写例子,大家可以看一下官方给出的例子 。例子中官方是用JsonTextWriter,但是你会发现它也只是用JsonWriter的API。关于JsonWriter 和JsonTextWriter ,大家可以点击其连接查看。如果你没有看懂官方的例子,后文我也会给出写Json的例子。我希望这时候我的例子可以帮助你。
接下来,我们修改一下之前转换Json的代码,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using Newtonsoft.Json;using UnityEngine;namespace JsonTest { public class NSToJsonProblem : MonoBehaviour { private void Start () { Vector3 a = Vector3.one; JsonSerializerSettings settings = new JsonSerializerSettings(); settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; settings.Converters.Add(new Vector3Convert()); string str = JsonConvert.SerializeObject(a, settings); Debug.Log(str); } } }
再次点击运行,我们就可以得到和JsonUtility一样的结果了。
PS:这里其实我们已经不用再设定ReferenceLoopHandling,因为我们自定义的转换器是没有做这样的操作。
在Unity中,继承MonoBehaviour的类型在使用Newtonsoft会报额外的问题。在这时候,我们使用Newtonsoft其实有些麻烦了。以我个人的项目经验,一般使用到Newtonsoft去转Json的时候,我都会极力避免这样的事情。毕竟我能少做事,为什么要做呢?不过这个问题的解决方案网络上也有了,我直接贴一下解决的链接 。不过链接中的代码可能因为版本不同的问题不能使用了,不过我依照他的思路写了下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 using Newtonsoft.Json;using Newtonsoft.Json.Serialization;using System;using System.Collections.Generic;using System.Linq;using UnityEngine;namespace JsonTest { public class ContractResolver : DefaultContractResolver { protected override IList<JsonProperty> CreateProperties (Type type, MemberSerialization memberSerialization ) { var properties = base .CreateProperties(type, memberSerialization); if (type.IsSubclassOf(typeof (MonoBehaviour))) { var info = CreateProperties(typeof (MonoBehaviour), memberSerialization); properties = properties.Where( (x) => { foreach (var property in info) { if (property.PropertyName == x.PropertyName) return false ; } return true ; }).ToList(); } return properties; } } }
调用案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 using Newtonsoft.Json;using UnityEngine;namespace JsonTest { public class NSJsonToObj : MonoBehaviour { public string json; private void Start () { var mono = this .gameObject.AddComponent<NewtonsoftMono>(); var setting = new JsonSerializerSettings(); setting.ContractResolver = new ContractResolver(); setting.NullValueHandling = NullValueHandling.Ignore; setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; Debug.Log(JsonConvert.SerializeObject(mono, setting)); } } }
如果项目是我来做,我遇到需要Mono转json的操作。我一定会将那些转Json的数据提出一个类或是结构体出来,然后将提出的东西进行json转换再赋值给Mono中对应的变量。
Json转对象
这里我以Vector3来举例子,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 using Newtonsoft.Json;using UnityEngine;namespace JsonTest { public class NSJsonToObj : MonoBehaviour { private void Start () { Vector3 a = Vector3.one; JsonSerializerSettings settings = new JsonSerializerSettings(); settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; settings.Converters.Add(new Vector3Convert()); string str = JsonConvert.SerializeObject(a, settings); Vector3 b = JsonConvert.DeserializeObject<Vector3>(str); Debug.Log(b); } } }
Newtonsoft进行json转对象的时候,并不会受到对象自身有自己类型的影响。一般而言DeserializeObject默认情况下就可以实现很多东西,但凡事总有例外比如这时候我们有一个需求从json中读出来的Vector3信息,x和y的值要进行转换。虽然我们是可以得到后再做这个事情,但是我们也放到转换器进行。接下来我们把Vector3Convert修改为下面的形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 using Newtonsoft.Json;using System;using UnityEngine;namespace JsonTest { public class Vector3Convert : JsonConverter { public override bool CanConvert (Type objectType ) { return objectType == typeof (Vector3); } public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) { if (reader.TokenType == JsonToken.Null) { return null ; } Vector3 v = Vector3.zero; while (reader.Read()) { if (reader.Value != null ) { if (reader.TokenType == JsonToken.PropertyName) { string pstr = (string )reader.Value; if (reader.Read() && reader.Value != null ) { switch (pstr) { case "x" : v.y = Convert.ToInt32(reader.Value); break ; case "y" : v.x = Convert.ToInt32(reader.Value); break ; case "z" : v.z = Convert.ToInt32(reader.Value); break ; } } } } } return v; } public override void WriteJson (JsonWriter writer, object value , JsonSerializer serializer ) { var vt3 = (Vector3)value ; writer.WriteStartObject(); writer.WritePropertyName("x" ); writer.WriteValue(vt3.x); writer.WritePropertyName("y" ); writer.WriteValue(vt3.y); writer.WritePropertyName("z" ); writer.WriteValue(vt3.z); writer.WriteEndObject(); } } }
然后我们修改一下之前的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using Newtonsoft.Json;using UnityEngine;namespace JsonTest { public class NSJsonToObj : MonoBehaviour { private void Start () { Vector3 a = Vector3.one; a.x = 10 ; JsonSerializerSettings settings = new JsonSerializerSettings(); settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; settings.Converters.Add(new Vector3Convert()); string str = JsonConvert.SerializeObject(a, settings); Vector3 b = JsonConvert.DeserializeObject<Vector3>(str, settings); Debug.Log(b); } } }
运行后,你就会发现b是(1.00, 10.00, 1.00)而不是a的(10.00, 1.00,
1.00)。
对于DeserializeObject,我本身用的也不算多。主要还是因为它本身功能对我来说就足够用了。所以我没遇到什么关于它的问题。这里有一个点要注意一下,对于继承于MonoBehaviour的类型,我们直接使用DeserializeObject会让Unity打出警告。
1 You are trying to create a MonoBehaviour using the 'new' keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all
这是因为DeserializeObject就像是JsonUtility.FromJson一样是创建一个新的对象。而Unity不允许继承于MonoBehaviour的类型通过new关键字来创建。不过,正如同JsonUtility提供了FromJsonOverwrite,Newtonsoft也提供了接口去实现这一点。下面我给出一个例子:
1 2 3 4 5 6 7 8 9 10 11 using Newtonsoft.Json;using UnityEngine;namespace JsonTest { public class NewtonsoftMono : MonoBehaviour { [JsonConverter(typeof(Vector3Convert)) ] public Vector3 test; } }
这里我使用了JsonConverter属性。这样之后转换为json或是从json转换回来,我都不需要再指定转换器了。
json信息
1 { "test" : { "x" : 10.0 , "y" : 20.0 , "z" : 30.0 } }
转换代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using Newtonsoft.Json;using UnityEngine;namespace JsonTest { public class NSJsonToObj : MonoBehaviour { public string json; private void Start () { var mono = this .gameObject.AddComponent<NewtonsoftMono>(); JsonConvert.PopulateObject(json, mono); Debug.Log(mono.test); } } }
关于枚举
无论是JsonUtility还是Newtonsoft,对于枚举,它们默认都是转换为数字的形式并以此为解析。我搜索了很久也没有找到JsonUtility自带的方法进行枚举类型以字符串形式与Json互相转换。网上大多数的做法还是和字典转换一样,他们都用多一个变量去进行枚举的转换。而Newtonsoft是有自带一个转换器,它名为StringEnumConverter。
其实枚举就算是数字的形式出现也挺好,只是后续如果有更改可能会麻烦一些。比如原本与0对应的None变成了与1对应。这时候字符串形式就比数字的形式更好了。不过,我更喜欢数字。因为我认为大多数情况下,数字占用的存储少一点。至于到底要怎么选择,这还是要看具体的项目要求和技术领导的习惯了。
使用Newtonsoft写Json
Newtonsoft提供了部分写Json的方法。我第一次接触的时候,我觉得这样写好繁琐,但是现在我认为这样写也还好。在我开发的过程中,我很少需要自己去写Json的。除非遇到需要我自己写转换器的时候。Newtonsoft官方也提供了部分案例:链接 。
其实主要就几个函数:
WriteStartObject和WriteEndObject
你可以把WriteStartObject当做是写入一个"{",而WriteEndObject是写入一个"}"。
WritePropertyName和WriteValue
在Json格式中,属性名和值的对应是下面的形式:
你会发现我并没有用双引号将值括起来。因为如果是数字类型,那么值就不需要双引号。因此我这里就干脆不括起来。而Newtonsoft优秀的点就在于它帮我们做好了这些判断。我们只需要先调用WritePropertyName写入属性名然后再调用WriteValue写入值就可以了。至于值要怎么写,那就是Newtonsoft要考虑的事情了。
WriteStartArray和WriteEndArray
WriteStartArray,你可以理解为写入一个"[",而它对应的结束就是WriteEndArray,即"]"。
WriteComment
WriteComment其实就是写注释。
WriteEnd
WriteEnd是Newtonsoft提供的可以代替WriteEndArray和WriteEndObject的函数。
WriteWhitespace
WriteWhitespace只接受只有空白行的字符串,即只存在'
','','等字符的字符串。否则它会报错。它就是在你想要的地方加一段空白行字符串。
我这里描述的都很简单,其实我也觉得就这样理解也够了。下面我是写的例子,里面也有对应的注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 using Newtonsoft.Json;using System;using System.IO;using System.Text;using UnityEngine;namespace JsonTest { public class NsWriteJson : MonoBehaviour { private void Start () { StringBuilder sb = new StringBuilder(); StringWriter sw = new StringWriter(sb); using (JsonWriter writer = new JsonTextWriter(sw)) { writer.Formatting = Formatting.Indented; writer.WriteStartObject(); writer.WriteWhitespace("\n\n" ); writer.WriteComment("这里只是写例子,所以属性名用中文" ); writer.WritePropertyName("今天的日期" ); writer.WriteValue(DateTime.Now); writer.WritePropertyName("str" ); writer.WriteValue("这是随便写的字符串" ); writer.WritePropertyName("arr" ); writer.WriteStartArray(); writer.WriteValue("arr1" ); writer.WriteValue("arr2" ); writer.WriteValue("arr3" ); writer.WriteEndArray(); writer.WriteEnd(); } Debug.Log(sb.ToString()); } } }
我们运行上面的代码,得到的结果如下:
1 2 3 4 5 6 7 8 9 10 11 { "今天的日期" : "2024-01-19T18:01:43.4517377+08:00" , "str" : "这是随便写的字符串" , "arr" : [ "arr1" , "arr2" , "arr3" ] }
WriteValue是支持一些转换,但是它本身也有判断。比如我们让他写一个List类型的变量,它会报下面的错误:
1 JsonWriterException: Unsupported type: System.Collections.Generic.List`1[System.String]. Use the JsonSerializer class to get the object's JSON representation. Path ''.
使用Newtonsoft读Json
Newtonsoft读Json官方也是有具体的例子:链接 。这个最主要的就是2点:
何时结束
当reader.Read()所返回的值为空时,那么就意味着已经结束了。
TokenType类型的判断
StartObject和EndObject就是对应'{'和'}',而StartArray和EndArray就是对应'['和']'。它们值都是null。PropertyName就是属性名字,其值就是属性的名字。一般来说属性名字后就是值。这时候TokenType就会变成对应值的类型。这里要注意一下JsonToken只有基础类型和字符串。它们值对应的就是它们本身值。
我不知道我这样描述是否能帮助到你,但请相信我尽全力描述了(我自己都感觉有点问题,但我真的尽力了o(╥﹏╥)o)。
总结
关于Json转换的第三方库,市面上还有很多。现在我写的这些只是我有使用到的。希望我写的这些例子可以帮到你。