C# JSON.net vs XPATH:如何在SelectTokens中保留节点顺序?
XPath2指出,选择的节点顺序应该按照它们在文档中的顺序返回。 在JSON.Net中选择tokens(JSONPath)时,情况似乎并非如此 当我处理以下文档时C# JSON.net vs XPATH:如何在SelectTokens中保留节点顺序?,c#,xpath,json.net,C#,Xpath,Json.net,XPath2指出,选择的节点顺序应该按照它们在文档中的顺序返回。 在JSON.Net中选择tokens(JSONPath)时,情况似乎并非如此 当我处理以下文档时 string json = @" { ""Files"": { ""dir1"": { ""Files"": { ""file1.1.txt"": { ""size:100""}, ""file1.2.txt"": { ""size:100""
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.*”)时,顺序如下
当我期望以下顺序时(作为Xpath//files/*)
我应该如何编写查询以获得XPath顺序的列表?除了修改Json.Net源代码之外,我无法直接控制返回结果的顺序。它似乎在使用广度优先排序 不必使用
SelectTokens()
,您可以在方法中使用LINQ到JSON查询。这将按深度优先顺序返回令牌。但是,您需要过滤掉您不感兴趣的属性名称,如“文件”和“大小”
stringjson=@”
{
“”文件“”:{
“dir1”:{
“”文件“”:{
“file1.1.txt”“:{”“size”“:100},
“file1.2.txt”“:{”“size”“:100}”
}
},
“dir2”:{
“”文件“”:{
“file2.1.txt”“:{”“size”“:100},
“file2.2.txt”“:{”“size”“:100}”
}
},
“file3.txt”“:{”“size”“:100}”
}
}";
JObject jo=JObject.Parse(json);
var files=jo.subjects()
第()类
.Select(p=>p.Name)
.Where(n=>n!=“文件”&&n!=“大小”)
.ToArray();
Console.WriteLine(string.Join(“\n”,files));
小提琴:
如果您不喜欢这种想法,另一种可能的解决方案是使用自定义将选定属性排序回其原始文档顺序:
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;
}
}
类JPropertyDocumentUserComparer:IComparer
{
公共整数比较(JProperty x、JProperty y)
{
var xa=GetFounders(x);
var ya=获得祖先(y);
对于(int i=0;i
像这样使用比较器:
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));
JObject jo=JObject.Parse(json);
var files=jo.SelectTokens($…文件)
第()类
.SelectMany(j=>j.Properties())
.OrderBy(p=>p,新的JPropertyDocumentUserComparer())
.Select(p=>p.Name)
.ToArray();
Console.WriteLine(string.Join(“\n”,files));
小提琴:这回答了我的问题。但是如果您稍微扩展一下它,就必须在Linq中将(复杂的)JSONPath转换为(一组)测试,这是相当痛苦的。遗憾的是,SelectTokens(JSONPath)没有按文档顺序返回节点,就像XPathI添加了另一个可能的解决方案一样,它使用自定义比较器将属性按文档顺序重新排序。也许这对你有用。我还没有测试第二个解决方案,但这看起来正是我需要的。非常感谢。
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));
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));