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

使用Newtonsoft JToken解析含$type、$i的多态JSON类型匹配问题

问题描述

我正尝试使用Newtonsoft的JToken将描述多态类型的JSON字符串格式化为键值对列表,该JSON开头以枚举形式列出了类型映射:

"$types":{
   "AIDataModels.LanguageModels.ProtoLanguageModels.RootDescriptor, AIDataModels, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null":"1",
   "AIDataModels.LanguageModels.ProtoLanguageModels.MicroDescriptor, AIDataModels, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null":"2",
   "AIDataModels.LanguageModels.ProtoLanguageModels.ProtoAttribute, AIDataModels, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null":"3",
   "AIDataModels.LanguageModels.ProtoLanguageModels.EnumElement, AIDataModels, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null":"4",
   "AIDataModels.LanguageModels.ProtoLanguageModels.EnumClass, AIDataModels, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null":"5",
   "AIDataModels.Summaries.ClassSummary, AIDataModels, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null":"6",
   "AIDataModels.Summaries.GeneralizeTypeSummary, AIDataModels, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null":"7"
}

但JSON中存在部分字段未直接标注类型引用的情况,正常引用类型的示例如下:

"GeneralizedType":{
   "$type":"4",
   "IsComposite":false,
   "Parent":{
      "$type":"5",
      "Key":"features",
      "Numeral":1
   },
   "Key":"part",
   "Numeral":29
}

未标注类型引用、仅出现$i标记的示例如下:

"GeneralizedType":{
   "$i":6
}

还有如下示例:外层对象标注$type为7,但内部GeneralizedType字段仅带$i标记,无类型枚举引用:

{
   "$type":"7",
   "GeneralizedType":{
      "$i":14
   },
   "Count":2
}

我知道这是JSON解析器做的冗余去除处理,但我不清楚如何将JSON中的这些"$i"值与对应类型关联,请问我遗漏了什么关键点?


解答

核心原因

该JSON是Newtonsoft.Json同时开启TypeNameHandling和PreserveReferencesHandling配置序列化生成的产物,你遗漏了实例ID映射规则:

  • $types是全局类型映射表,实现完整类型名到短数字标识的映射,对应每个实例首次出现时标注的$type字段值,用来快速识别多态类型。
  • 每个首次出现的实例对象都会额外携带$id字段,值为唯一整数ID。同一个实例后续在其他位置被引用时,序列化器不会重复输出全量属性,只会输出{"$i": 对应实例的$id值}作为引用标记,避免冗余。

解决步骤

  1. 预遍历所有JToken节点,构建全局实例映射表Dictionary<int, JObject>:遍历所有JObject节点,只要存在$id字段,就将$id的整数值作为key,当前JObject作为value存入字典。
  2. 遇到带$i的引用节点时,直接用$i的值查映射表拿到原始实例对象,原始实例中携带的$type值即可对应到$types表中的具体类型。
  3. 如果原始实例中也嵌套了$i引用,按照同样逻辑递归处理即可。

参考代码示例

// 预构建实例ID映射
var idMap = new Dictionary<int, JObject>();
void Traverse(JToken node)
{
    if (node is JObject obj)
    {
        if (obj.TryGetValue("$id", out var idVal) && idVal.Type == JTokenType.Integer)
        {
            idMap[idVal.Value<int>()] = obj;
        }
        foreach (var child in obj.Values())
        {
            Traverse(child);
        }
    }
    else if (node is JArray arr)
    {
        foreach (var item in arr)
        {
            Traverse(item);
        }
    }
}

// 解析JSON并调用遍历逻辑
JToken root = JToken.Parse(yourJsonString);
Traverse(root);

// 处理$i引用示例
JObject refNode = root["GeneralizedType"] as JObject;
if (refNode.TryGetValue("$i", out var iVal) && idMap.TryGetValue(iVal.Value<int>(), out var originalObj))
{
    // 拿到原始实例的$type,再对应$types表即可获取具体类型
    string typeId = originalObj["$type"].Value<string>();
}

内容的提问来源于stack exchange,提问作者Donald

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 20:18:01