Unity JsonUtility 序列化链表和字典
前言
为什么要用 JsonUtility 呢?
第三方的 Json 库很多是不适配最新版 Unity 的, 另外就是对于强迫症而言, 难道你不想用 Unity 官方的吗, 难道不想自动更新吗? 一直需要手动更新也是很麻烦的.
一般来说第三方的 Json 库都是不支持序列化
MonoBehaviour
类和ScriptableObject
类的.据 Unity 手册上描述, JsonUtility 比目前流行的 .NET JSON 解决方案要快得多, 缺点就是 JsonUtility 提供的功能很少. 下面是 Unity 手册的原文:
垃圾收集 (GC) 内存使用量为最低量:
ToJson
仅为返回的字符串分配 GC 内存.FromJson
仅为返回的对象以及所需的所有子对象分配 GC 内存 (例如: 如果对包含数组的对象进行反序列>化,则 Unity 将为该数组分配 GC 内存)FromJsonOverwrite
仅根据需要为写入的字段 (例如字符串和数组)分配 GC 内存. 这意味着, 如果 JSON >覆盖的所有字段都是值类型, 则 Unity 不会分配任何 GC 内存.- 可以使用后台线程中的 JsonUtility API. 但是, 与任何多线程代码一样, 在一个线程上序列化或反序列化对>象时, 请勿在另一个线程上访问或更改该对象.
Unity 手册: Json 序列化
JsonUtility 使用条件
JsonUtility 支持任何
MonoBehaviour
子类,ScriptableObject
子类或者带有 [Serializable] 属性的普通类或结构. 但是, 将 JSON 反序列化为MonoBehaviour
或ScriptableObject
子类时,必须使用FromJsonOverwrite
方法, 如果尝试使用 FromJson 则 Unity 会抛出异常.将对象传入到标准 Unity 序列化程序进行处理时, 需要遵循与在 Inspector 中相同的规则和限制, 比如: Unity 只序列化字段, 不序列化属性;
另外此 API 不支持类似
Dictionary<>
的类型; 也不支持将其他类型直接传递到 API, 例如原始类型或数组. 如果需要转换上述类型, 则需要将它们包裹在某种 class 或 struct 中.
API 链接
https://docs.unity3d.com/cn/2020.2/ScriptReference/JsonUtility.html
另外还有一个编辑器模式下专用的 API: EditorJsonUtility
它的链接是: https://docs.unity3d.com/cn/2020.2/ScriptReference/EditorJsonUtility.html
如何使用 JsonUtility 序列化数组 [] 和链表 List<>
由于 JsonUtility 不支持直接序列化数组和链表, 因此需要首先将其包装在一个 class 或者 struct 中, 之后对 class 或者 struct 进行序列化.
以 List<> 和 class 为例. 使用特性 [Serializable] 修饰 class, 使 class 成为可序列化类型, 最后使用 [SerializeField] 修饰 List<>, 这样链表就可以被序列化成 Json 文本了.
1 | [ ] |
上面的字符串链表中的元素类型是 string, 是可以序列化的, 另一个成员 int 类型, 也可以序列化, 只要将类修饰为 [Serializable], 字段修饰为 [SerializeField] 便可以使用 JsonUtility 进行序列化了.
注: 严格来说, 如果基础类型使用的是 public 修饰, 那么就不必使用 [SerializeField] 进行修饰, 如上面的 id 成员, 但是链表不是基础类型, 即使使用 public 修饰, 也必须使用 [SerializeField] 进行修饰.
1 | [ ] |
但是如果 id 是 private 类型的, 那么还是需要使用 [SerializeField] 进行修饰. 基础类型的序列化规则和 Inspector 面板的序列化规则是相同的.
如何使用 JsonUtility 序列化字典 Dictionary<>
字典即使使用上述方式也是无法进行序列化的, 这里需要使用到 Unity 提供的 ISerializationCallbackReceiver
接口.
这个接口要求实现两个方法: OnBeforeSerialize()
和 OnAfterDeserialize()
.
public void OnBeforeSerialize()
这个方法会在 序列化之前
调用.
public void OnAfterDeserialize()
这个方法会在 反序列化之后
调用.
我们的思路是: 将字典放到一个新的 class 中, 由于字典不能序列化, 但是链表可以通过使用 [SerializeField] 修饰来进行序列化, 因此可以使用两个 List<> 分别保存所有的键和所有的值, 并使用 [SerializeField] 进行修饰, 这样只要在序列化之前将字典中的值放到链表中即可, 最后序列化出来的结果就是两个链表.
1 | [ ] |
由于序列化后是两个链表, 那么反序列化出来的数据也是两个链表, 此时就需要将这两个链表转换为字典.
1 | public void OnAfterDeserialize() |
最后再补一个将反序列化的字典返回的方法.
1 | public Dictionary<TKey, TValue> ToDictionary() |
使用方法:
1 | // 序列化 |
参考链接
【Unity】JsonUtility で List