使用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值}作为引用标记,避免冗余。
解决步骤
- 预遍历所有JToken节点,构建全局实例映射表
Dictionary<int, JObject>:遍历所有JObject节点,只要存在$id字段,就将$id的整数值作为key,当前JObject作为value存入字典。 - 遇到带
$i的引用节点时,直接用$i的值查映射表拿到原始实例对象,原始实例中携带的$type值即可对应到$types表中的具体类型。 - 如果原始实例中也嵌套了
$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
相关产品推荐
相关产品推荐

