You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 11:07:19