Unity中使用Newtonsoft.Json反序列化JSON为Trie结构的问题
我有一个18MB名为JsonFileAsset的JSON文件,是Trie结构的嵌套字典表示,特性如下:
- "@" 代表字符串的结束标记
- "#" 代表Trie中的子节点
- "$" 代表
true,表示一个单词的结尾 - "%" 代表
false,表示不是单词的结尾
JSON文件示例:
{"@":"%","#":{"a":{"@":"%","#":{"a":{"@":"%","#":{"h":{"@":"$","#":{"e":{"@":"%","#":{"d": ...
我尝试在TrieHandler中实现反序列化,代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; using Newtonsoft.Json; public class TrieHandler { private Dictionary<string, object> trieDict; public void LoadTrieFromJson(string jsonContent) { trieDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonContent); if (trieDict == null) { Debug.LogError("Failed to parse JSON content: " + jsonContent); } Debug.Log("TrieDict size: " + trieDict.Count); } public bool IsWordValid(string word) { return IsWordInTrie(trieDict, word, 0); } private bool IsWordInTrie(Dictionary<string, object> node, string word, int index) { if (index == word.Length) { // 检查当前节点是否是单词结尾 return node.ContainsKey("@"); } char currentChar = word[index]; if (!node.ContainsKey("#")) return false; // 没有子节点,单词不存在 Dictionary<string, object> childrenDict = (Dictionary<string, object>)node["#"]; if (!childrenDict.ContainsKey(currentChar.ToString())) return false; // 当前字符不在子节点中 return IsWordInTrie(childrenDict[currentChar.ToString()] as Dictionary<string, object>, word, index + 1); } } public class GameplayController : MonoBehaviour { public string selectedTileLetters = ""; // Trie处理器实例 private TrieHandler trieHandler; private string jsonContent; void Start() { trieHandler = new TrieHandler(); TextAsset jsonFile = Resources.Load<TextAsset>("JsonFileAsset"); if (jsonFile != null) { trieHandler.LoadTrieFromJson(jsonFile.text); } else { Debug.LogError("Failed to load JSON file"); } } ... // 其他代码省略 }
使用Unity原生JsonUtility时,trieDict.Count输出0;使用Newtonsoft.Json的JsonConvert.DeserializeObject方法时,count为2,但实际文件包含400k+键值对,且IsWordValid方法从未返回true。经测试Trie遍历算法正常,问题仅出在LoadTrieFromJson的反序列化过程,请问如何修复该反序列化逻辑?
1. 问题根源
用Dictionary<string, object>反序列化嵌套Trie结构时,Newtonsoft.Json会将深层子节点默认解析为JObject而非Dictionary<string, object>,导致强制类型转换失败,遍历逻辑无法正确访问深层节点。另外,IsWordValid方法仅判断@键是否存在,未检查其值是否为$(单词结尾标记),这也是方法始终返回false的核心原因之一。
2. 修复步骤
(1)配置反序列化规则,强制解析为字典
修改LoadTrieFromJson方法,通过Newtonsoft.Json的转换器让所有嵌套JSON对象都转为Dictionary<string, object>:
using Newtonsoft.Json.Converters; // 需要添加这个命名空间 public void LoadTrieFromJson(string jsonContent) { var settings = new JsonSerializerSettings { Converters = new List<JsonConverter> { new ExpandoObjectConverter() } }; // 强制将所有JSON对象解析为Dictionary<string, object> trieDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonContent, settings); if (trieDict == null) { Debug.LogError("Failed to parse JSON content"); } Debug.Log("TrieDict size: " + trieDict.Count); }
(2)修正单词结尾的判断逻辑
原方法仅检查@键是否存在,需改为判断@的值是否为$才是有效单词结尾,同时添加安全类型转换避免空引用错误:
private bool IsWordInTrie(Dictionary<string, object> node, string word, int index) { if (index == word.Length) { // 检查当前节点的@值是否为$,即有效单词结尾 if (node.TryGetValue("@", out object endMarker)) { return endMarker.ToString() == "$"; } return false; } char currentChar = word[index]; if (!node.ContainsKey("#")) return false; // 安全转换子节点字典,避免类型转换失败 if (!(node["#"] is Dictionary<string, object> childrenDict)) return false; string charKey = currentChar.ToString(); if (!childrenDict.ContainsKey(charKey)) return false; // 安全转换下一层节点 if (!(childrenDict[charKey] is Dictionary<string, object> nextNode)) return false; return IsWordInTrie(nextNode, word, index + 1); }
(3)Unity原生JsonUtility的适配方案(可选)
如果想使用Unity原生工具,需自定义Trie节点类匹配JSON结构,避免直接用字典:
[System.Serializable] public class TrieNode { public string @"@"; // 转义@符号,避免语法冲突 public Dictionary<string, TrieNode> @"#"; // 子节点字典 } // 修改TrieHandler中的反序列化和遍历逻辑 private TrieNode rootNode; public void LoadTrieFromJson(string jsonContent) { rootNode = JsonUtility.FromJson<TrieNode>(jsonContent); if (rootNode == null) { Debug.LogError("Failed to parse JSON content"); } } public bool IsWordValid(string word) { return IsWordInTrie(rootNode, word, 0); } private bool IsWordInTrie(TrieNode node, string word, int index) { if (index == word.Length) { return node.@"@" == "$"; } char currentChar = word[index]; if (node.@"#" == null || !node.@"#".ContainsKey(currentChar.ToString())) return false; return IsWordInTrie(node.@"#"[currentChar.ToString()], word, index + 1); }
3. 额外优化建议
- 18MB的JSON文件建议异步加载,避免阻塞主线程:
async void Start() { trieHandler = new TrieHandler(); var request = Resources.LoadAsync<TextAsset>("JsonFileAsset"); await request; TextAsset jsonFile = request.asset as TextAsset; if (jsonFile != null) { trieHandler.LoadTrieFromJson(jsonFile.text); } else { Debug.LogError("Failed to load JSON file"); } }
- 可将JSON转为二进制格式,大幅减少加载和解析耗时,提升运行性能。
内容的提问来源于stack exchange,提问作者swiftenjoyer

