JSON.net与XPATH:如何在SelectTokens中保留节点顺序? [英] JSON.net vs XPATH: How to preserve node order in SelectTokens?
问题描述
XPath 2指出选择的节点顺序应按其在文档中的顺序返回. 当您在JSON.Net中选择SelectTokens(JSONPath)时,情况并非如此
XPath 2 states that the nodes order of a selection should be returned in their order in the document. It looks this is not the case when you SelectTokens(JSONPath) in JSON.Net
当我处理以下文档时
string json = @"
{
""Files"": {
""dir1"": {
""Files"": {
""file1.1.txt"": {
""size:100""},
""file1.2.txt"": {
""size:100""}
}
},
""dir2"": {
""Files"": {
""file2.1.txt"": {
""size:100""},
""file2.2.txt"": {
""size:100""}
}
},
""file3.txt"": {
""size:100""}
}
}";
使用JSON.net SelectTokens("$ .. files.*")时的顺序如下:
The order is the following when using JSON.net SelectTokens("$..files.*")
dir1
dir2
file3.txt
file1.1.txt
file1.2.txt
file2.1.txt
file2.2.txt
当我期望以下顺序时(如Xpath//files/*)
When I expected the following order (as Xpath //files/*)
dir1
file1.1.txt
file1.2.txt
dir2
file2.1.txt
file2.2.txt
file3.txt
如何编写查询,以便按XPath顺序获得列表?
How should I write my query so that I get a List in the XPath order ?
推荐答案
修改Json.Net源代码的简短操作,没有一种方法可以直接控制
Short of modifying the Json.Net source code, there is not a way that I can see to directly control what order SelectTokens()
returns its results. It appears to be using breadth-first ordering.
代替使用SelectTokens()
,您可以将LINQ-to-JSON查询与
Instead of using SelectTokens()
, you could use a LINQ-to-JSON query with the Descendants()
method. This will return tokens in depth-first order. However, you would need to filter out the property names you are not interested in, like "Files" and "size".
string json = @"
{
""Files"": {
""dir1"": {
""Files"": {
""file1.1.txt"": { ""size"": 100 },
""file1.2.txt"": { ""size"": 100 }
}
},
""dir2"": {
""Files"": {
""file2.1.txt"": { ""size"": 100 },
""file2.2.txt"": { ""size"": 100 }
}
},
""file3.txt"": { ""size"": 100 }
}
}";
JObject jo = JObject.Parse(json);
var files = jo.Descendants()
.OfType<JProperty>()
.Select(p => p.Name)
.Where(n => n != "Files" && n != "size")
.ToArray();
Console.WriteLine(string.Join("\n", files));
提琴: https://dotnetfiddle.net/yRAev4
If you don't like that idea, another possible solution is to use a custom IComparer<T>
to sort the selected properties back into their original document order after the fact:
class JPropertyDocumentOrderComparer : IComparer<JProperty>
{
public int Compare(JProperty x, JProperty y)
{
var xa = GetAncestors(x);
var ya = GetAncestors(y);
for (int i = 0; i < xa.Count && i < ya.Count; i++)
{
if (!ReferenceEquals(xa[i], ya[i]))
{
return IndexInParent(xa[i]) - IndexInParent(ya[i]);
}
}
return xa.Count - ya.Count;
}
private List<JProperty> GetAncestors(JProperty prop)
{
return prop.AncestorsAndSelf().OfType<JProperty>().Reverse().ToList();
}
private int IndexInParent(JProperty prop)
{
int i = 0;
var parent = (JObject)prop.Parent;
foreach (JProperty p in parent.Properties())
{
if (ReferenceEquals(p, prop)) return i;
i++;
}
return -1;
}
}
像这样使用比较器:
JObject jo = JObject.Parse(json);
var files = jo.SelectTokens("$..Files")
.OfType<JObject>()
.SelectMany(j => j.Properties())
.OrderBy(p => p, new JPropertyDocumentOrderComparer())
.Select(p => p.Name)
.ToArray();
Console.WriteLine(string.Join("\n", files));
提琴: https://dotnetfiddle.net/xhx7Kk
这篇关于JSON.net与XPATH:如何在SelectTokens中保留节点顺序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!