C# 如何使用Newtonsoft JSON.NET处理JSON文档中的对象引用?
我有一个json数据集,它带有标准数据字段和引用字段。它看起来像这样:C# 如何使用Newtonsoft JSON.NET处理JSON文档中的对象引用?,c#,.net,json,json.net,deserialization,C#,.net,Json,Json.net,Deserialization,我有一个json数据集,它带有标准数据字段和引用字段。它看起来像这样: [ { "id":1, "name":"Book", "description":"Something you can read" }, { "id":2, "name":"newspaper", "description": { "ref":"0.description"
[
{
"id":1,
"name":"Book",
"description":"Something you can read"
},
{
"id":2,
"name":"newspaper",
"description": {
"ref":"0.description"
}
}
]
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);
}
}
这是我的数据模型:
public class PhysicalObject {
[Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Default)]
public int id;
[Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default)]
public string name;
[Newtonsoft.Json.JsonProperty("description", Required = Newtonsoft.Json.Required.Default)] // FIXED should have been description not desc
public string desc;
}
json文件中的每个属性都有一个特定的类型,例如int表示id,string表示description,但是每个属性也可以通过ref链接到另一个属性。在这种情况下,id=2的描述与id=1相同
是否有一种方法可以通过错误处理或创建某种回退反序列化来应用,从而允许我序列化ref
请注意,由于其他要求,我必须使用Newtonsoft Json.NET库来解决此问题。有关解决此问题的其他库或技术的信息非常有用,但可能无法解决此问题。您需要某种机制来解决这些引用 以下是两种方法: 1.使用内置的引用处理 其中一种机制是Newtonsoft serializer的属性,它与您描述的完全相同,只是它查找$id和$ref,而不是id和ref 要使用它,您可以在将JSON树转换为类型化对象之前对其进行转换,方法是首先使用将其读入JSON树表示,然后遍历此树,将id和ref属性替换为$id和$ref。由于中间JSON树本质上是可修改和动态的,因此您可以轻松地执行此操作 然后,您可以使用内置的引用解析机制将此转换树转换为类型化对象,方法是使用获取转换树上的JsonReader,您可以使用它来指示它将其反序列化为所需的类型
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);
}
}
}
编辑:还可以查看哪个允许您从字符串或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了,所以请原谅我的语法错误或非惯用用法。但这是总体思路。您需要一些机制来解决这些引用 以下是两种方法: 1.使用内置的引用处理 其中一种机制是Newtonsoft serializer的属性,它与您描述的完全相同,只是它查找$id和$ref,而不是id和ref 要使用它,您可以在将JSON树转换为类型化对象之前对其进行转换,方法是首先使用将其读入JSON树表示,然后遍历此树,将id和ref属性替换为$id和$ref。由于中间JSON树本质上是可修改和动态的,因此您可以轻松地执行此操作 然后,您可以使用内置的引用解析机制将此转换树转换为类型化对象,方法是使用获取转换树上的JsonReader,您可以使用它来指示它将其反序列化为所需的类型
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);
}
}
}
编辑:还可以查看哪个允许您从字符串或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了,所以请原谅我的语法错误或非惯用用法。但这是一般的想法。您可以将JSON预加载到层次结构中,然后使用{ref:some.period separated.path}形式的对象替换为路径中指示的标记。随后,JToken层次结构可以反序列化到最终模型 以下扩展方法可以实现此目的:
public static partial class JsonExtensions
{
const string refPropertyName = "ref";
public static void ResolveRefererences(JToken root)
{
if (!(root is JContainer container))
return;
var refs = container.Descendants().OfType<JObject>().Where(o => IsRefObject(o)).ToList();
Console.WriteLine(JsonConvert.SerializeObject(refs));
foreach (var refObj in refs)
{
var path = GetRefObjectValue(refObj);
var original = ResolveRef(root, path);
if (original != null)
refObj.Replace(original);
}
}
static bool IsRefObject(JObject obj)
{
return GetRefObjectValue(obj) != null;
}
static string GetRefObjectValue(JObject obj)
{
if (obj.Count == 1)
{
var refValue = obj[refPropertyName];
if (refValue != null && refValue.Type == JTokenType.String)
{
return (string)refValue;
}
}
return null;
}
static JToken ResolveRef(JToken token, string path)
{
// TODO: determine whether it is possible for a property name to contain a '.' character, and if so, how the path will look.
var components = path.Split('.');
foreach (var component in components)
{
if (token is JObject obj)
token = obj[component];
else if (token is JArray array)
token = token[int.Parse(component, NumberFormatInfo.InvariantInfo)];
else
// Or maybe just return null?
throw new JsonException("Unexpected token type.");
}
return token;
}
}
然后您将按如下方式使用它:
// Load into intermediate JToken hierarchy; do not perform DateTime recognition yet.
var root = JsonConvert.DeserializeObject<JToken>(jsonString, new JsonSerializerSettings { DateParseHandling = DateParseHandling.None });
// Replace {"ref": "...") objects with their references.
JsonExtensions.ResolveRefererences(root);
// Deserialize directly to final model. DateTime recognition should get performed now.
var list = root.ToObject<List<PhysicalObject>>();
注:
此解决方案不尝试保留引用,即使反序列化的{ref:some.period separated.path}引用与反序列化的原始实例相同的实例。虽然Json.NET确实具有通过$ref和$id属性访问的功能,但它有几个限制,包括:
它不处理对基本体的引用,只处理对象和数组
它不允许向前引用,只允许向后引用。这个问题不清楚JSON中的ref属性是否会引用文档后面的值
这些限制将使将问题中显示的引用语法转换为Json.NET语法变得复杂
最好推迟到最后的反序列化。如果您的模型具有字符串属性,其JSON值可能恰好看起来像ISO 8601日期,那么过早的日期识别可能会导致字符串值被修改
演示小提琴。您可以将JSON预加载到层次结构中,然后使用路径中指示的标记替换形式为{ref:some.period separated.path}的对象。随后,JToken层次结构可以反序列化到最终模型 以下扩展方法不适用于 他说: 然后您将按如下方式使用它:
// Load into intermediate JToken hierarchy; do not perform DateTime recognition yet.
var root = JsonConvert.DeserializeObject<JToken>(jsonString, new JsonSerializerSettings { DateParseHandling = DateParseHandling.None });
// Replace {"ref": "...") objects with their references.
JsonExtensions.ResolveRefererences(root);
// Deserialize directly to final model. DateTime recognition should get performed now.
var list = root.ToObject<List<PhysicalObject>>();
注:
此解决方案不尝试保留引用,即使反序列化的{ref:some.period separated.path}引用与反序列化的原始实例相同的实例。虽然Json.NET确实具有通过$ref和$id属性访问的功能,但它有几个限制,包括:
它不处理对基本体的引用,只处理对象和数组
它不允许向前引用,只允许向后引用。这个问题不清楚JSON中的ref属性是否会引用文档后面的值
这些限制将使将问题中显示的引用语法转换为Json.NET语法变得复杂
最好推迟到最后的反序列化。如果您的模型具有字符串属性,其JSON值可能恰好看起来像ISO 8601日期,那么过早的日期识别可能会导致字符串值被修改
演示小提琴。您想要反序列化的数据模型是什么?如果您只是将描述声明为一个对象,那么Json.NET会将其反序列化为某种内容—字符串或JObject(视情况而定)。@dbc感谢您的回复。我将数据模型添加到原始帖子中。我知道我可以使用DeserializeAnonymousType对其进行反序列化,并将数据放入通用对象中。但是,我想将它直接输入到特定类型的对象中。如果只有一个通用对象,将描述转换为通用对象会有所帮助。。。然而,这是一个超级简单的例子,每个对象有30个字段,并且有很多级别,所有这些字段都可以是特定类型或引用。最后,我希望两个对象都有相同的字符串,但它们不必都指向内存中的同一字符串。只要object1.description和object2.description是包含您可以读取的内容的字符串,那么一切都将是完美的。如果您实际上不需要保留引用,并且可以克隆引用的值,那么您可以执行以下快速而肮脏的操作:。但这似乎与Avish的答案一致,因此我不确定是否有必要再添加一个。@dbc很抱歉,我正试图挤出时间来实施您的解决方案。如果你能回答这个问题,我会选择它。我能使你的小提琴符合比这个样品更复杂的实际情况。它包括不同类型的引用和引用数组等。最终,由于灵活性,这个解决方案对我来说是最好的。您想要反序列化的数据模型是什么?如果您只是将描述声明为一个对象,那么Json.NET会将其反序列化为某种内容—字符串或JObject(视情况而定)。@dbc感谢您的回复。我将数据模型添加到原始帖子中。我知道我可以使用DeserializeAnonymousType对其进行反序列化,并将数据放入通用对象中。但是,我想将它直接输入到特定类型的对象中。如果只有一个通用对象,将描述转换为通用对象会有所帮助。。。然而,这是一个超级简单的例子,每个对象有30个字段,并且有很多级别,所有这些字段都可以是特定类型或引用。最后,我希望两个对象都有相同的字符串,但它们不必都指向内存中的同一字符串。只要object1.description和object2.description是包含您可以读取的内容的字符串,那么一切都将是完美的。如果您实际上不需要保留引用,并且可以克隆引用的值,那么您可以执行以下快速而肮脏的操作:。但这似乎与Avish的答案一致,因此我不确定是否有必要再添加一个。@dbc很抱歉,我正试图挤出时间来实施您的解决方案。如果你能回答这个问题,我会选择它。我能使你的小提琴符合比这个样品更复杂的实际情况。它包括不同类型的引用和引用数组等。最终,这个解决方案对我来说是最好的,因为它具有灵活性。这非常有帮助,尽管比我希望的要痛苦一些。为了清晰起见,快速提问:transformedJsonTree实际上是jsonTree正确的吗?只是一个伪代码?关于Json.NET的PreserveReferencesHandling功能有两点:1它不处理对原语的引用,只处理对象和数组。2它不允许正向引用,只允许反向引用。OP的JSON中的ref属性是否会引用文档后面的值,这个问题并不清楚。如果他们这样做了,那么转换为$ref将不起作用,您需要对值进行重新排序,并使$id对象位于第一位。@KivakWolf是的,我在SO编辑器中写了这篇文章,所以希望出现错误和遗漏。文中描述了总体思路;这段代码只是为了说明这样的东西会是什么样子。@dbc谢谢,我不知道
那个在这种情况下,自产自销的方法可能更有意义。这非常有帮助,尽管比我希望的要痛苦一些。为了清晰起见,快速提问:transformedJsonTree实际上是jsonTree正确的吗?只是一个伪代码?关于Json.NET的PreserveReferencesHandling功能有两点:1它不处理对原语的引用,只处理对象和数组。2它不允许正向引用,只允许反向引用。OP的JSON中的ref属性是否会引用文档后面的值,这个问题并不清楚。如果他们这样做了,那么转换为$ref将不起作用,您需要对值进行重新排序,并使$id对象位于第一位。@KivakWolf是的,我在SO编辑器中写了这篇文章,所以希望出现错误和遗漏。文中描述了总体思路;这段代码只是为了说明这样的东西会是什么样子。@dbc谢谢,我不知道。在这种情况下,自滚方法可能更有意义。在我的回答中,使用LINQ到JSON查找ref对象比递归方法要好得多。但是请注意,您的解析逻辑尝试解析根文档的引用路径,而OP的场景有引用路径,其中第一个元素是文档中其他对象的$id,这是一个不同的用例。阅读我看到的其他注释可能误解了原始需求,所以OP必须澄清。除此之外,我们的答案基本相同。如果我的问题有点含糊,我道歉。我想要的是能够解析出引用并构建它,而不是使用一个封闭的系统,因为我的引用变量非常复杂,引用有时是ID,有时是元素,有时是混合物。虽然两个答案都是完全正确的,但我还是选择了这个答案,因为它让我能够很好地控制我需要的参考资料。除此之外,我还必须添加大量代码,但这是一个非常好的模板。使用LINQ to JSON查找ref对象比我的答案中的递归方法要好得多。但是请注意,您的解析逻辑尝试解析根文档的引用路径,而OP的场景有引用路径,其中第一个元素是文档中其他对象的$id,这是一个不同的用例。阅读我看到的其他注释可能误解了原始需求,所以OP必须澄清。除此之外,我们的答案基本相同。如果我的问题有点含糊,我道歉。我想要的是能够解析出引用并构建它,而不是使用一个封闭的系统,因为我的引用变量非常复杂,引用有时是ID,有时是元素,有时是混合物。虽然两个答案都是完全正确的,但我还是选择了这个答案,因为它让我能够很好地控制我需要的参考资料。除此之外,我还必须添加大量代码,但这就像一个伟大的锅炉板。