JSON.net vs XPATH:如何在SelectTokens中保留节点顺序?

XPath 2声明选择的节点顺序应按其在文档中的顺序返回。 当您在JSON.Net中使用SelectTokens(JSONPath)时,情况并非如此

当我处理以下文件时

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。*”)时的顺序如下

 dir1 dir2 file3.txt file1.1.txt file1.2.txt file2.1.txt file2.2.txt 

当我期望以下顺序(作为Xpath // files / *)

 dir1 file1.1.txt file1.2.txt dir2 file2.1.txt file2.2.txt file3.txt 

我应该如何编写查询以便在XPath命令中获得List?

如果没有修改Json.Net源代码,我无法直接控制SelectTokens()返回其结果的顺序。 它似乎使用广度优先排序。

您可以使用带有Descendants()方法的LINQ-to-JSON查询,而不是使用SelectTokens() 。 这将以深度优先顺序返回令牌。 但是,您需要过滤掉您不感兴趣的属性名称,例如“文件”和“大小”。

 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() .Select(p => p.Name) .Where(n => n != "Files" && n != "size") .ToArray(); Console.WriteLine(string.Join("\n", files)); 

小提琴: https : //dotnetfiddle.net/yRAev4


如果您不喜欢这个想法,另一个可能的解决方案是使用自定义IComparer在事实之后将所选属性重新排序回原始文档顺序:

 class JPropertyDocumentOrderComparer : IComparer { 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 GetAncestors(JProperty prop) { return prop.AncestorsAndSelf().OfType().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() .SelectMany(j => j.Properties()) .OrderBy(p => p, new JPropertyDocumentOrderComparer()) .Select(p => p.Name) .ToArray(); Console.WriteLine(string.Join("\n", files)); 

小提琴: https : //dotnetfiddle.net/xhx7Kk