JSON.net vs XPATH: How to preserve node order in SelectTokens?

a 夏天 提交于 2019-12-10 17:19:32

问题


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

When I process the following document

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""}
  }
}";

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

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

How should I write my query so that I get a List in the XPath order ?


回答1:


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.

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));

Fiddle: 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;
    }
}

Use the comparer like this:

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));

Fiddle: https://dotnetfiddle.net/xhx7Kk



来源:https://stackoverflow.com/questions/51772089/json-net-vs-xpath-how-to-preserve-node-order-in-selecttokens

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!