您将需要某种机制来解析这些引用。
这里有两种方法:
1. 使用内置引用处理
Newtonsoft 串行器就是这样一种机制PreserveReferencesHandling https://www.newtonsoft.com/json/help/html/PreserveObjectReferences.htm属性,它的作用与您所描述的完全一样,除了它寻找$id
and $ref
代替id
and ref
.
要使用此功能,您可以在将 JSON 树转换为类型化对象之前对其进行转换,方法是首先将其读入 JSON 树表示形式(使用JToken.Parse https://www.newtonsoft.com/json/help/html/ParseJsonAny.htm),然后遍历这棵树,替换id
and ref
属性与$id
and $ref
(由于中间 JSON 树本质上是可修改且动态的,因此您可以轻松地做到这一点)。
然后,您可以使用内置的引用解析机制将此转换后的树转换为类型对象,方法是使用JObject.CreateReader https://www.newtonsoft.com/json/help/html/CreateReader.htm获得一个JsonReader
在变形后的树上,你可以给它JsonSerializer.Deserialize<T> https://www.newtonsoft.com/json/help/html/Overload_Newtonsoft_Json_JsonSerializer_Deserialize.htm指示它将其反序列化为您想要的类型。
T DeserializeJsonWithReferences<T>(string input)
{
var jsonTree = JToken.Parse(jsonString);
TransformJsonTree(jsonTree); // renames `id` and `ref` properties in-place
var jsonReader = jsonTree.CreateReader();
var jsonSerializer = new JsonSerializer() {
PreserveReferencesHandling = PreserveReferenceHandling.All
};
var deserialized = jsonSerializer.Deserialize<T>(jsonReader);
return deserialized;
}
void TransformJsonTree(JToken token)
{
var container = token as JContainer;
if (container == null)
return;
foreach (propName in SpecialPropertyNames) // {"id", "ref"}
{
objects = container
.Descendants()
.OfType<JObject>()
.Where(x => x.ContainsKey(propName));
foreach (obj in objects)
{
obj["$" + propName] = obj[propName];
obj.Remove(propName);
}
}
}
2.滚动你自己的参考分辨率层
如果您想自己完成,则可以使用更复杂的方法:您需要添加自己的参考解析层,该层会在将 JSON 树转换为类型化对象之前对其进行转换。
同样,您可以首先将 JSON 流读入 JSON 树表示形式。然后你需要遍历该树两次:
在第一次遍历时,您将寻找带有id
属性,并将它们记录在字典中(来自id
到包含它的对象)。
在第二次遍历时,您将寻找带有ref
属性,并通过按其引用对象查找引用对象,将这些引用对象替换为适当的值id
在您之前创建的字典中,然后根据中描述的属性链导航其属性ref
价值。例如,如果参考是3.address.city
,您将查找 ID 为 3 的对象,然后找到其值address
属性,然后是值city
该值的属性,这将是引用的最终值。
一旦 JSON 树被转换并且所有引用对象都被替换为相应的引用值,您就可以将 JSON 树转换为类型化对象。
从代码角度来看,这与前面的示例完全相同,除了transformJsonTree
,而不是仅仅重命名id
and ref
属性,您必须实现实际的查找和引用解析逻辑。
它可能看起来像这样:
IDictionary<string, JToken> BuildIdMap(JContainer container)
{
return container
.Descendants()
.OfType<JObject>()
.Where(obj => obj.ContainsKey(IdPropertyName)
.ToDictionary(obj => obj[IdPropertyName], obj => obj);
}
JToken LookupReferenceValue(string referenceString, IDictionary<string, JObject> idToObjectMap)
{
var elements = referenceString.Split('.');
var obj = idToObjectMap(elements[0]);
for (int i = 1; i < elements.Length; i++)
{
var elem = elements[i];
switch (obj)
{
case JArray jarr:
obj = arr[elem]; // elem is a property name
break;
case JObject jobj:
obj = jobj[int.Parse(elem)]; // elem is an array index
break;
default:
throw Exception("You should throw a meaningful exception here");
}
}
}
void ResolveReferences(JContainer container, IDictionary<string, JObject> idToObjectMap)
{
refObjects = container
.Descendants()
.OfType<JObject>()
.Where(obj.Count == 1 && obj => obj.ContainsKey(RefPropertyName))
foreach (var refObject in refObjects)
{
referenceString = refObject[RefPropertyName];
referencedValue = LookupReferenceValue(refObject, idToObjectMap)
refObject.Replace(referencedValue);
}
}
编辑:另请看一下JToken.SelectToken https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Linq_JToken_SelectToken.htm它允许您从字符串或JsonPath
,从上面节省了很多麻烦(假设文档中的引用语法与 Newtonsoft 支持的语法相匹配,例如关于数组索引)。
JToken LookupReferenceValue(string referenceString, IDictionary<string, JObject> idToObjectMap)
{
var parts = referenceString.Split('.', 1); // only split on first '.'
var id = parts[0];
var tokenPath = parts[1];
var referencedObject = idToObjectMap[id];
var referencedValue = referencedObject.SelectToken(tokenPath);
return referencedValue;
}
我已经好几年没有写任何 C# 了,所以请原谅任何语法错误或不惯用的用法。但这是一般的想法。