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