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

时间:2018-08-09 16:37:21

标签: c# xpath json.net

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顺序获得列表?

1 个答案:

答案 0 :(得分:2)

简短地修改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<JProperty>()
              .Select(p => p.Name)
              .Where(n => n != "Files" && n != "size")
              .ToArray();

Console.WriteLine(string.Join("\n", files));

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


如果您不喜欢该主意,另一种可能的解决方案是使用自定义IComparer<T>将选定的属性归类为事实后的原始文档顺序:

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