-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] [TestFixture]
public class JPathExecuteTests : TestFixtureBase 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] [Test]
public void ScanQuoted() public void ScanQuoted()
{ {
@@ -202,6 +202,29 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
Assert.AreEqual("Blah", ((ScanFilter)path.Filters[0]).Name); 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] [Test]
public void WildcardScanWithRoot() public void WildcardScanWithRoot()
{ {
+22 -12
View File
@@ -216,7 +216,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
} }
else if (_expression[_currentIndex] == '?') else if (_expression[_currentIndex] == '?')
{ {
return ParseQuery(indexerCloseChar); return ParseQuery(indexerCloseChar, scan);
} }
else else
{ {
@@ -394,7 +394,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
} }
} }
private PathFilter ParseQuery(char indexerCloseChar) private PathFilter ParseQuery(char indexerCloseChar, bool scan)
{ {
_currentIndex++; _currentIndex++;
EnsureLength("Path ended with open indexer."); EnsureLength("Path ended with open indexer.");
@@ -406,7 +406,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
_currentIndex++; _currentIndex++;
QueryExpression expression = ParseExpression(); QueryExpression expression = ParseExpression(scan);
_currentIndex++; _currentIndex++;
EnsureLength("Path ended with open indexer."); 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]); 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] == '$') if (_expression[_currentIndex] == '$')
{ {
@@ -455,12 +465,12 @@ namespace Newtonsoft.Json.Linq.JsonPath
return new JsonException("Unexpected character while parsing path query: " + _expression[_currentIndex]); return new JsonException("Unexpected character while parsing path query: " + _expression[_currentIndex]);
} }
private object ParseSide() private object ParseSide(bool scan)
{ {
EatWhitespace(); EatWhitespace();
List<PathFilter> expressionPath; List<PathFilter> expressionPath;
if (TryParseExpression(out expressionPath)) if (TryParseExpression(scan, out expressionPath))
{ {
EatWhitespace(); EatWhitespace();
EnsureLength("Path ended with open query."); EnsureLength("Path ended with open query.");
@@ -480,14 +490,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
throw CreateUnexpectedCharacterException(); throw CreateUnexpectedCharacterException();
} }
private QueryExpression ParseExpression() private QueryExpression ParseExpression(bool scan)
{ {
QueryExpression rootExpression = null; QueryExpression rootExpression = null;
CompositeExpression parentExpression = null; CompositeExpression parentExpression = null;
while (_currentIndex < _expression.Length) while (_currentIndex < _expression.Length)
{ {
object left = ParseSide(); object left = ParseSide(scan);
object right = null; object right = null;
QueryOperator op; QueryOperator op;
@@ -501,7 +511,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
{ {
op = ParseOperator(); op = ParseOperator();
right = ParseSide(); right = ParseSide(scan);
} }
BooleanQueryExpression booleanExpression = new BooleanQueryExpression BooleanQueryExpression booleanExpression = new BooleanQueryExpression
@@ -51,5 +51,33 @@ namespace Newtonsoft.Json.Linq.JsonPath
return null; 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 value = c;
JToken container = c; JContainer container = c as JContainer;
while (true) while (true)
{ {
if (container != null && container.HasValues) value = GetNextScanValue(c, container, value);
if (value == null)
{ {
value = container.First; break;
}
else
{
while (value != null && value != c && value == value.Parent.Last)
{
value = value.Parent;
}
if (value == null || value == c)
{
break;
}
value = value.Next;
} }
JProperty e = value as JProperty; JProperty e = value as JProperty;
@@ -11,27 +11,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
foreach (JToken c in current) foreach (JToken c in current)
{ {
JToken value = c; JToken value = c;
JToken container = c; JContainer container = c as JContainer;
while (true) while (true)
{ {
if (container != null && container.HasValues) value = GetNextScanValue(c, container, value);
if (value == null)
{ {
value = container.First; break;
}
else
{
while (value != null && value != c && value == value.Parent.Last)
{
value = value.Parent;
}
if (value == null || value == c)
{
break;
}
value = value.Next;
} }
JProperty e = value as JProperty; JProperty e = value as JProperty;