-Fixed JSONPath recursive descent queries

This commit is contained in:
James Newton-King
2017-04-26 11:09:15 +12:00
parent c80be730f8
commit b3d514b617
7 changed files with 232 additions and 46 deletions
@@ -51,6 +51,123 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
[TestFixture]
public class JPathExecuteTests : TestFixtureBase
{
[Test]
public void ScanFilter()
{
string json = @"{
""elements"": [
{
""id"": ""A"",
""children"": [
{
""id"": ""AA"",
""children"": [
{
""id"": ""AAA""
},
{
""id"": ""AAB""
}
]
},
{
""id"": ""AB""
}
]
},
{
""id"": ""B"",
""children"": []
}
]
}";
JObject models = JObject.Parse(json);
var results = models.SelectTokens("$.elements..[?(@.id=='AAA')]").ToList();
Assert.AreEqual(1, results.Count);
Assert.AreEqual(models["elements"][0]["children"][0]["children"][0], results[0]);
}
[Test]
public void FilterTrue()
{
string json = @"{
""elements"": [
{
""id"": ""A"",
""children"": [
{
""id"": ""AA"",
""children"": [
{
""id"": ""AAA""
},
{
""id"": ""AAB""
}
]
},
{
""id"": ""AB""
}
]
},
{
""id"": ""B"",
""children"": []
}
]
}";
JObject models = JObject.Parse(json);
var results = models.SelectTokens("$.elements[?(true)]").ToList();
Assert.AreEqual(2, results.Count);
Assert.AreEqual(results[0], models["elements"][0]);
Assert.AreEqual(results[1], models["elements"][1]);
}
[Test]
public void ScanFilterTrue()
{
string json = @"{
""elements"": [
{
""id"": ""A"",
""children"": [
{
""id"": ""AA"",
""children"": [
{
""id"": ""AAA""
},
{
""id"": ""AAB""
}
]
},
{
""id"": ""AB""
}
]
},
{
""id"": ""B"",
""children"": []
}
]
}";
JObject models = JObject.Parse(json);
var results = models.SelectTokens("$.elements..[?(true)]").ToList();
Assert.AreEqual(25, results.Count);
}
[Test]
public void ScanQuoted()
{
@@ -202,6 +202,29 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
Assert.AreEqual("Blah", ((ScanFilter)path.Filters[0]).Name);
}
[Test]
public void QueryTrue()
{
JPath path = new JPath("$.elements[?(true)]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("elements", ((FieldFilter)path.Filters[0]).Name);
Assert.AreEqual(QueryOperator.Exists, ((QueryFilter)path.Filters[1]).Expression.Operator);
}
[Test]
public void ScanQuery()
{
JPath path = new JPath("$.elements..[?(@.id=='AAA')]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("elements", ((FieldFilter)path.Filters[0]).Name);
BooleanQueryExpression expression = (BooleanQueryExpression)((QueryScanFilter) path.Filters[1]).Expression;
List<PathFilter> paths = (List<PathFilter>)expression.Left;
Assert.IsInstanceOf(typeof(FieldFilter), paths[0]);
}
[Test]
public void WildcardScanWithRoot()
{
+22 -12
View File
@@ -216,7 +216,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
else if (_expression[_currentIndex] == '?')
{
return ParseQuery(indexerCloseChar);
return ParseQuery(indexerCloseChar, scan);
}
else
{
@@ -394,7 +394,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
}
private PathFilter ParseQuery(char indexerCloseChar)
private PathFilter ParseQuery(char indexerCloseChar, bool scan)
{
_currentIndex++;
EnsureLength("Path ended with open indexer.");
@@ -406,7 +406,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
_currentIndex++;
QueryExpression expression = ParseExpression();
QueryExpression expression = ParseExpression(scan);
_currentIndex++;
EnsureLength("Path ended with open indexer.");
@@ -417,13 +417,23 @@ namespace Newtonsoft.Json.Linq.JsonPath
throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]);
}
return new QueryFilter
if (!scan)
{
Expression = expression
};
return new QueryFilter
{
Expression = expression
};
}
else
{
return new QueryScanFilter
{
Expression = expression
};
}
}
private bool TryParseExpression(out List<PathFilter> expressionPath)
private bool TryParseExpression(bool scan, out List<PathFilter> expressionPath)
{
if (_expression[_currentIndex] == '$')
{
@@ -455,12 +465,12 @@ namespace Newtonsoft.Json.Linq.JsonPath
return new JsonException("Unexpected character while parsing path query: " + _expression[_currentIndex]);
}
private object ParseSide()
private object ParseSide(bool scan)
{
EatWhitespace();
List<PathFilter> expressionPath;
if (TryParseExpression(out expressionPath))
if (TryParseExpression(scan, out expressionPath))
{
EatWhitespace();
EnsureLength("Path ended with open query.");
@@ -480,14 +490,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
throw CreateUnexpectedCharacterException();
}
private QueryExpression ParseExpression()
private QueryExpression ParseExpression(bool scan)
{
QueryExpression rootExpression = null;
CompositeExpression parentExpression = null;
while (_currentIndex < _expression.Length)
{
object left = ParseSide();
object left = ParseSide(scan);
object right = null;
QueryOperator op;
@@ -501,7 +511,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
{
op = ParseOperator();
right = ParseSide();
right = ParseSide(scan);
}
BooleanQueryExpression booleanExpression = new BooleanQueryExpression
@@ -51,5 +51,33 @@ namespace Newtonsoft.Json.Linq.JsonPath
return null;
}
}
protected static JToken GetNextScanValue(JToken originalParent, JToken container, JToken value)
{
// step into container's values
if (container != null && container.HasValues)
{
value = container.First;
}
else
{
// finished container, move to parent
while (value != null && value != originalParent && value == value.Parent.Last)
{
value = value.Parent;
}
// finished
if (value == null || value == originalParent)
{
return null;
}
// move to next value in container
value = value.Next;
}
return value;
}
}
}
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal class QueryScanFilter : PathFilter
{
public QueryExpression Expression { get; set; }
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
{
foreach (JToken t in current)
{
if (t is JContainer c)
{
foreach (JToken d in c.DescendantsAndSelf())
{
if (Expression.IsMatch(root, d))
{
yield return d;
}
}
}
else
{
if (Expression.IsMatch(root, t))
{
yield return t;
}
}
}
}
}
}
@@ -16,27 +16,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
JToken value = c;
JToken container = c;
JContainer container = c as JContainer;
while (true)
{
if (container != null && container.HasValues)
value = GetNextScanValue(c, container, value);
if (value == null)
{
value = container.First;
}
else
{
while (value != null && value != c && value == value.Parent.Last)
{
value = value.Parent;
}
if (value == null || value == c)
{
break;
}
value = value.Next;
break;
}
JProperty e = value as JProperty;
@@ -11,27 +11,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
foreach (JToken c in current)
{
JToken value = c;
JToken container = c;
JContainer container = c as JContainer;
while (true)
{
if (container != null && container.HasValues)
value = GetNextScanValue(c, container, value);
if (value == null)
{
value = container.First;
}
else
{
while (value != null && value != c && value == value.Parent.Last)
{
value = value.Parent;
}
if (value == null || value == c)
{
break;
}
value = value.Next;
break;
}
JProperty e = value as JProperty;