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("字典并没有被赋值");
}
}
}

其结果为:

1
字典并没有被赋值

如果我们真的需要去使用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。关于JsonWriterJsonTextWriter,大家可以点击其连接查看。如果你没有看懂官方的例子,后文我也会给出写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官方也提供了部分案例:链接

        其实主要就几个函数:

  1. WriteStartObject和WriteEndObject

        你可以把WriteStartObject当做是写入一个"{",而WriteEndObject是写入一个"}"。

  1. WritePropertyName和WriteValue

        在Json格式中,属性名和值的对应是下面的形式:

1
"属性名":

你会发现我并没有用双引号将值括起来。因为如果是数字类型,那么值就不需要双引号。因此我这里就干脆不括起来。而Newtonsoft优秀的点就在于它帮我们做好了这些判断。我们只需要先调用WritePropertyName写入属性名然后再调用WriteValue写入值就可以了。至于值要怎么写,那就是Newtonsoft要考虑的事情了。

  1. WriteStartArray和WriteEndArray

        WriteStartArray,你可以理解为写入一个"[",而它对应的结束就是WriteEndArray,即"]"。

  1. WriteComment

        WriteComment其实就是写注释。

  1. WriteEnd

        WriteEnd是Newtonsoft提供的可以代替WriteEndArray和WriteEndObject的函数。

  1. 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); // 写入它的值 DateTime的值可以直接转换
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点:

  1. 何时结束

        当reader.Read()所返回的值为空时,那么就意味着已经结束了。

  1. TokenType类型的判断

        StartObject和EndObject就是对应'{'和'}',而StartArray和EndArray就是对应'['和']'。它们值都是null。PropertyName就是属性名字,其值就是属性的名字。一般来说属性名字后就是值。这时候TokenType就会变成对应值的类型。这里要注意一下JsonToken只有基础类型和字符串。它们值对应的就是它们本身值。

我不知道我这样描述是否能帮助到你,但请相信我尽全力描述了(我自己都感觉有点问题,但我真的尽力了o(╥﹏╥)o)。

总结

        关于Json转换的第三方库,市面上还有很多。现在我写的这些只是我有使用到的。希望我写的这些例子可以帮到你。