-Fixed JSONPath recursive descent queries
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user