-JsonPath++

This commit is contained in:
James Newton-King
2013-11-19 11:18:26 -08:00
parent 042c2b3024
commit 1d9154f32f
9 changed files with 560 additions and 160 deletions
@@ -25,7 +25,9 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Newtonsoft.Json.Linq.JsonPath;
using Newtonsoft.Json.Tests.Bson;
#if !NETFX_CORE
using NUnit.Framework;
#else
@@ -404,6 +406,19 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
Assert.AreEqual(3, (int)t[1]);
}
[Test]
public void MultipleQueries()
{
JArray a = new JArray(1, 2, 3, 4, 5, 6, 7, 8, 9);
// json path does item based evaluation - http://www.sitepen.com/blog/2008/03/17/jsonpath-support/
// first query resolves array to ints
// int has no children to query
IList<JToken> t = a.SelectTokens("[?(@ <> 1)][?(@ <> 4)][?(@ < 7)]").ToList();
Assert.IsNotNull(t);
Assert.AreEqual(0, t.Count);
}
[Test]
public void GreaterQuery()
{
@@ -419,20 +434,37 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 3)), t[1]));
}
[Test]
public void GreaterQueryBigInteger()
{
JArray a = new JArray(
new JObject(new JProperty("hi", new BigInteger(1))),
new JObject(new JProperty("hi", new BigInteger(2))),
new JObject(new JProperty("hi", new BigInteger(3))));
IList<JToken> t = a.SelectTokens("[ ?( @.hi > 1 ) ]").ToList();
Assert.IsNotNull(t);
Assert.AreEqual(2, t.Count);
Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 2)), t[0]));
Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 3)), t[1]));
}
[Test]
public void GreaterOrEqualQuery()
{
JArray a = new JArray(
new JObject(new JProperty("hi", 1)),
new JObject(new JProperty("hi", 2)),
new JObject(new JProperty("hi", 2.0)),
new JObject(new JProperty("hi", 3)));
IList<JToken> t = a.SelectTokens("[ ?( @.hi >= 1 ) ]").ToList();
Assert.IsNotNull(t);
Assert.AreEqual(3, t.Count);
Assert.AreEqual(4, t.Count);
Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 1)), t[0]));
Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 2)), t[1]));
Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 3)), t[2]));
Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 2.0)), t[2]));
Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 3)), t[3]));
}
[Test]
@@ -109,6 +109,14 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
() => { new JPath("$ .Blah"); });
}
[Test]
public void NoFieldNameAfterDot()
{
ExceptionAssert.Throws<JsonException>(
@"Unexpected end while parsing path.",
() => { new JPath("$.Blah."); });
}
[Test]
public void RootWithBadWhitespace2()
{
@@ -215,11 +223,10 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
JPath path = new JPath("Blah[ ?( @..name ) ]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
List<QueryExpression> expressions = ((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(1, expressions.Count);
Assert.AreEqual(QueryOperator.Exists, expressions[0].Operator);
Assert.AreEqual(1, expressions[0].Path.Count);
Assert.AreEqual("name", ((ScanFilter)expressions[0].Path[0]).Name);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(QueryOperator.Exists, expressions.Operator);
Assert.AreEqual(1, expressions.Path.Count);
Assert.AreEqual("name", ((ScanFilter)expressions.Path[0]).Name);
}
[Test]
@@ -228,10 +235,9 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
JPath path = new JPath("Blah[ ?( @.name=='hi' ) ]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
List<QueryExpression> expressions = ((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(1, expressions.Count);
Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator);
Assert.AreEqual("hi", (string)expressions[0].Value);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(QueryOperator.Equals, expressions.Operator);
Assert.AreEqual("hi", (string)expressions.Value);
}
[Test]
@@ -240,10 +246,9 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
JPath path = new JPath(@"Blah[ ?( @.name=='h\'i' ) ]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
List<QueryExpression> expressions = ((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(1, expressions.Count);
Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator);
Assert.AreEqual("h'i", (string)expressions[0].Value);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(QueryOperator.Equals, expressions.Operator);
Assert.AreEqual("h'i", (string)expressions.Value);
}
[Test]
@@ -252,10 +257,9 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
JPath path = new JPath(@"Blah[ ?( @.name=='h\\i' ) ]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
List<QueryExpression> expressions = ((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(1, expressions.Count);
Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator);
Assert.AreEqual("h\\i", (string)expressions[0].Value);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(QueryOperator.Equals, expressions.Operator);
Assert.AreEqual("h\\i", (string)expressions.Value);
}
[Test]
@@ -272,10 +276,9 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
JPath path = new JPath("Blah[ ?( @.name==false ) ]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
List<QueryExpression> expressions = ((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(1, expressions.Count);
Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator);
Assert.AreEqual(false, (bool)expressions[0].Value);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(QueryOperator.Equals, expressions.Operator);
Assert.AreEqual(false, (bool)expressions.Value);
}
[Test]
@@ -284,10 +287,9 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
JPath path = new JPath("Blah[ ?( @.name==true ) ]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
List<QueryExpression> expressions = ((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(1, expressions.Count);
Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator);
Assert.AreEqual(true, (bool)expressions[0].Value);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(QueryOperator.Equals, expressions.Operator);
Assert.AreEqual(true, (bool)expressions.Value);
}
[Test]
@@ -296,98 +298,184 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
JPath path = new JPath("Blah[ ?( @.name==null ) ]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
List<QueryExpression> expressions = ((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(1, expressions.Count);
Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator);
Assert.AreEqual(null, expressions[0].Value.Value);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(QueryOperator.Equals, expressions.Operator);
Assert.AreEqual(null, expressions.Value.Value);
}
[Test]
public void FilterWithScan()
{
JPath path = new JPath("[?(@..name<>null)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual("name", ((ScanFilter)expressions[0].Path[0]).Name);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual("name", ((ScanFilter)expressions.Path[0]).Name);
}
[Test]
public void FilterWithNotEquals()
{
JPath path = new JPath("[?(@.name<>null)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.NotEquals, expressions[0].Operator);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.NotEquals, expressions.Operator);
}
[Test]
public void FilterWithNotEquals2()
{
JPath path = new JPath("[?(@.name!=null)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.NotEquals, expressions[0].Operator);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.NotEquals, expressions.Operator);
}
[Test]
public void FilterWithLessThan()
{
JPath path = new JPath("[?(@.name<null)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.LessThan, expressions[0].Operator);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.LessThan, expressions.Operator);
}
[Test]
public void FilterWithLessThanOrEquals()
{
JPath path = new JPath("[?(@.name<=null)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.LessThanOrEquals, expressions[0].Operator);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.LessThanOrEquals, expressions.Operator);
}
[Test]
public void FilterWithGreaterThan()
{
JPath path = new JPath("[?(@.name>null)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.GreaterThan, expressions[0].Operator);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.GreaterThan, expressions.Operator);
}
[Test]
public void FilterWithGreaterThanOrEquals()
{
JPath path = new JPath("[?(@.name>=null)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.GreaterThanOrEquals, expressions[0].Operator);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.GreaterThanOrEquals, expressions.Operator);
}
[Test]
public void FilterWithInteger()
{
JPath path = new JPath("[?(@.name>=12)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(12, (int)expressions[0].Value);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(12, (int)expressions.Value);
}
[Test]
public void FilterWithNegativeInteger()
{
JPath path = new JPath("[?(@.name>=-12)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(-12, (int)expressions[0].Value);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(-12, (int)expressions.Value);
}
[Test]
public void FilterWithFloat()
{
JPath path = new JPath("[?(@.name>=12.1)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(12.1d, (double)expressions[0].Value);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(12.1d, (double)expressions.Value);
}
[Test]
public void FilterExistWithAnd()
{
JPath path = new JPath("[?(@.name&&@.title)]");
CompositeExpression expressions = (CompositeExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.And, expressions.Operator);
Assert.AreEqual(2, expressions.Expressions.Count);
Assert.AreEqual("name", ((FieldFilter)((BooleanQueryExpression)expressions.Expressions[0]).Path[0]).Name);
Assert.AreEqual(QueryOperator.Exists, expressions.Expressions[0].Operator);
Assert.AreEqual("title", ((FieldFilter)((BooleanQueryExpression)expressions.Expressions[1]).Path[0]).Name);
Assert.AreEqual(QueryOperator.Exists, expressions.Expressions[1].Operator);
}
[Test]
public void FilterExistWithAndOr()
{
JPath path = new JPath("[?(@.name&&@.title||@.pie)]");
CompositeExpression andExpression = (CompositeExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(QueryOperator.And, andExpression.Operator);
Assert.AreEqual(2, andExpression.Expressions.Count);
Assert.AreEqual("name", ((FieldFilter)((BooleanQueryExpression)andExpression.Expressions[0]).Path[0]).Name);
Assert.AreEqual(QueryOperator.Exists, andExpression.Expressions[0].Operator);
CompositeExpression orExpression = (CompositeExpression)andExpression.Expressions[1];
Assert.AreEqual(2, orExpression.Expressions.Count);
Assert.AreEqual("title", ((FieldFilter)((BooleanQueryExpression)orExpression.Expressions[0]).Path[0]).Name);
Assert.AreEqual(QueryOperator.Exists, orExpression.Expressions[0].Operator);
Assert.AreEqual("pie", ((FieldFilter)((BooleanQueryExpression)orExpression.Expressions[1]).Path[0]).Name);
Assert.AreEqual(QueryOperator.Exists, orExpression.Expressions[1].Operator);
}
[Test]
public void BadOr1()
{
ExceptionAssert.Throws<JsonException>("Unexpected character while parsing path query: )",
() => new JPath("[?(@.name||)]"));
}
[Test]
public void BaddOr2()
{
ExceptionAssert.Throws<JsonException>("Unexpected character while parsing path query: |",
() => new JPath("[?(@.name|)]"));
}
[Test]
public void BaddOr3()
{
ExceptionAssert.Throws<JsonException>("Unexpected character while parsing path query: |",
() => new JPath("[?(@.name|"));
}
[Test]
public void BaddOr4()
{
ExceptionAssert.Throws<JsonException>("Path ended with open query.",
() => new JPath("[?(@.name||"));
}
[Test]
public void NoAtAfterOr()
{
ExceptionAssert.Throws<JsonException>("Unexpected character while parsing path query: s",
() => new JPath("[?(@.name||s"));
}
[Test]
public void NoPathAfterAt()
{
ExceptionAssert.Throws<JsonException>(@"Path ended with open query.",
() => new JPath("[?(@.name||@"));
}
[Test]
public void NoPathAfterDot()
{
ExceptionAssert.Throws<JsonException>(@"Unexpected end while parsing path.",
() => new JPath("[?(@.name||@."));
}
[Test]
public void NoPathAfterDot2()
{
ExceptionAssert.Throws<JsonException>(@"Unexpected end while parsing path.",
() => new JPath("[?(@.name||@.)]"));
}
[Test]
public void FilterWithFloatExp()
{
JPath path = new JPath("[?(@.name>=5.56789e+0)]");
List<QueryExpression> expressions = ((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(5.56789e+0, (double)expressions[0].Value);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression;
Assert.AreEqual(5.56789e+0, (double)expressions.Value);
}
[Test]
@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Linq.JsonPath;
using NUnit.Framework;
namespace Newtonsoft.Json.Tests.Linq.JsonPath
{
[TestFixture]
public class QueryExpressionTests : TestFixtureBase
{
[Test]
public void AndExpressionTest()
{
CompositeExpression compositeExpression = new CompositeExpression
{
Operator = QueryOperator.And,
Expressions = new List<QueryExpression>
{
new BooleanQueryExpression
{
Operator = QueryOperator.Exists,
Path = new List<PathFilter>
{
new FieldFilter
{
Name = "FirstName"
}
}
},
new BooleanQueryExpression
{
Operator = QueryOperator.Exists,
Path = new List<PathFilter>
{
new FieldFilter
{
Name = "LastName"
}
}
}
}
};
JObject o1 = new JObject
{
{"Title","Title!"},
{"FirstName", "FirstName!"},
{"LastName", "LastName!"}
};
Assert.IsTrue(compositeExpression.IsMatch(o1));
JObject o2 = new JObject
{
{"Title","Title!"},
{"FirstName", "FirstName!"}
};
Assert.IsFalse(compositeExpression.IsMatch(o2));
JObject o3 = new JObject
{
{"Title","Title!"}
};
Assert.IsFalse(compositeExpression.IsMatch(o3));
}
[Test]
public void OrExpressionTest()
{
CompositeExpression compositeExpression = new CompositeExpression
{
Operator = QueryOperator.Or,
Expressions = new List<QueryExpression>
{
new BooleanQueryExpression
{
Operator = QueryOperator.Exists,
Path = new List<PathFilter>
{
new FieldFilter
{
Name = "FirstName"
}
}
},
new BooleanQueryExpression
{
Operator = QueryOperator.Exists,
Path = new List<PathFilter>
{
new FieldFilter
{
Name = "LastName"
}
}
}
}
};
JObject o1 = new JObject
{
{"Title","Title!"},
{"FirstName", "FirstName!"},
{"LastName", "LastName!"}
};
Assert.IsTrue(compositeExpression.IsMatch(o1));
JObject o2 = new JObject
{
{"Title","Title!"},
{"FirstName", "FirstName!"}
};
Assert.IsTrue(compositeExpression.IsMatch(o2));
JObject o3 = new JObject
{
{"Title","Title!"}
};
Assert.IsFalse(compositeExpression.IsMatch(o3));
}
}
}
@@ -211,6 +211,7 @@
<Compile Include="Linq\JsonPath\JPathParseTests.cs" />
<Compile Include="Linq\JPropertyTests.cs" />
<Compile Include="Linq\JRawTests.cs" />
<Compile Include="Linq\JsonPath\QueryExpressionTests.cs" />
<Compile Include="Linq\JTokenEqualityComparerTests.cs" />
<Compile Include="Linq\JTokenReaderTest.cs" />
<Compile Include="Linq\JTokenTests.cs" />
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal class AndFilter : PathFilter
{
public List<PathFilter> Filters { get; set; }
public override IEnumerable<JToken> ExecuteFilter(IEnumerable<JToken> current, bool errorWhenNoMatch)
{
foreach (PathFilter f in Filters)
{
current = f.ExecuteFilter(current, errorWhenNoMatch).ToList();
}
return current;
}
}
}
+110 -56
View File
@@ -85,6 +85,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
{
bool scan = false;
bool followingIndexer = false;
bool followingDot = false;
bool ended = false;
while (_currentIndex < _expression.Length && !ended)
@@ -107,6 +108,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
_currentIndex++;
currentPartStartIndex = _currentIndex;
followingIndexer = true;
followingDot = false;
break;
case ']':
case ')':
@@ -135,9 +137,10 @@ namespace Newtonsoft.Json.Linq.JsonPath
_currentIndex++;
currentPartStartIndex = _currentIndex;
followingIndexer = false;
followingDot = true;
break;
default:
if (query && (currentChar == '=' || currentChar == '<' || currentChar == '!' || currentChar == '>'))
if (query && (currentChar == '=' || currentChar == '<' || currentChar == '!' || currentChar == '>' || currentChar == '|' || currentChar == '&'))
{
ended = true;
}
@@ -152,6 +155,8 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
}
bool atPathEnd = (_currentIndex == _expression.Length);
if (_currentIndex > currentPartStartIndex)
{
string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex).TrimEnd();
@@ -160,8 +165,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member };
filters.Add(filter);
}
else
{
// no field name following dot in path and at end of base path/query
if (followingDot && (atPathEnd || query))
throw new JsonException("Unexpected end while parsing path.");
}
return (_currentIndex == _expression.Length);
return atPathEnd;
}
private PathFilter ParseIndexer(char indexerOpenChar)
@@ -344,75 +355,118 @@ namespace Newtonsoft.Json.Linq.JsonPath
_currentIndex++;
List<QueryExpression> expressions = new List<QueryExpression>();
QueryExpression expression = ParseExpression();
while (true)
_currentIndex++;
EnsureLength("Path ended with open indexer.");
EatWhitespace();
if (_expression[_currentIndex] != indexerCloseChar)
throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]);
return new QueryFilter
{
expressions.Add(ParseExpression());
if (_expression[_currentIndex] == ')')
{
_currentIndex++;
EnsureLength("Path ended with open indexer.");
EatWhitespace();
if (_expression[_currentIndex] != indexerCloseChar)
throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]);
return new QueryFilter
{
Expression = expressions
};
}
else
{
_currentIndex++;
}
}
Expression = expression
};
}
private QueryExpression ParseExpression()
{
EatWhitespace();
QueryExpression rootExpression = null;
CompositeExpression parentExpression = null;
if (_expression[_currentIndex] != '@')
throw new JsonException("Unexpected character while parsing path query: " + _expression[_currentIndex]);
_currentIndex++;
List<PathFilter> expressionPath = new List<PathFilter>();
if (ParsePath(expressionPath, _currentIndex, true))
throw new JsonException("Path ended with open query.");
EatWhitespace();
EnsureLength("Path ended with open query.");
QueryOperator op;
object value = null;
if (_expression[_currentIndex] == ')')
while (_currentIndex < _expression.Length)
{
op = QueryOperator.Exists;
}
else
{
op = ParseOperator();
EatWhitespace();
if (_expression[_currentIndex] != '@')
throw new JsonException("Unexpected character while parsing path query: " + _expression[_currentIndex]);
_currentIndex++;
List<PathFilter> expressionPath = new List<PathFilter>();
if (ParsePath(expressionPath, _currentIndex, true))
throw new JsonException("Path ended with open query.");
EatWhitespace();
EnsureLength("Path ended with open query.");
value = ParseValue();
QueryOperator op;
object value = null;
if (_expression[_currentIndex] == ')'
|| _expression[_currentIndex] == '|'
|| _expression[_currentIndex] == '&')
{
op = QueryOperator.Exists;
}
else
{
op = ParseOperator();
EatWhitespace();
EnsureLength("Path ended with open query.");
EatWhitespace();
EnsureLength("Path ended with open query.");
value = ParseValue();
EatWhitespace();
EnsureLength("Path ended with open query.");
}
BooleanQueryExpression booleanExpression = new BooleanQueryExpression
{
Path = expressionPath,
Operator = op,
Value = (op != QueryOperator.Exists) ? new JValue(value) : null
};
if (_expression[_currentIndex] == ')')
{
if (parentExpression != null)
{
parentExpression.Expressions.Add(booleanExpression);
return rootExpression;
}
return booleanExpression;
}
if (_expression[_currentIndex] == '&' && Match("&&"))
{
if (parentExpression == null || parentExpression.Operator != QueryOperator.And)
{
CompositeExpression andExpression = new CompositeExpression { Operator = QueryOperator.And };
if (parentExpression != null)
parentExpression.Expressions.Add(andExpression);
parentExpression = andExpression;
if (rootExpression == null)
rootExpression = parentExpression;
}
parentExpression.Expressions.Add(booleanExpression);
}
if (_expression[_currentIndex] == '|' && Match("||"))
{
if (parentExpression == null || parentExpression.Operator != QueryOperator.Or)
{
CompositeExpression orExpression = new CompositeExpression { Operator = QueryOperator.Or };
if (parentExpression != null)
parentExpression.Expressions.Add(orExpression);
parentExpression = orExpression;
if (rootExpression == null)
rootExpression = parentExpression;
}
parentExpression.Expressions.Add(booleanExpression);
}
}
return new QueryExpression
{
Path = expressionPath,
Operator = op,
Value = (op != QueryOperator.Exists) ? new JValue(value) : null
};
throw new JsonException("Path ended with open query.");
}
private object ParseValue()
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal class OrFilter : PathFilter
{
public List<PathFilter> Filters { get; set; }
public override IEnumerable<JToken> ExecuteFilter(IEnumerable<JToken> current, bool errorWhenNoMatch)
{
List<JToken> cachedCurrent = current.ToList();
List<JToken> remaining = cachedCurrent.ToList();
List<JToken> matches = new List<JToken>();
int previousResultCount = 0;
foreach (PathFilter f in Filters)
{
matches.AddRange(f.ExecuteFilter(remaining, errorWhenNoMatch));
for (int i = previousResultCount; i < matches.Count; i++)
{
remaining.Remove(matches[i]);
}
previousResultCount = matches.Count;
}
return cachedCurrent.Where(matches.Contains);
}
}
}
@@ -13,19 +13,103 @@ namespace Newtonsoft.Json.Linq.JsonPath
LessThan,
LessThanOrEquals,
GreaterThan,
GreaterThanOrEquals
GreaterThanOrEquals,
And,
Or
}
internal class QueryExpression
internal abstract class QueryExpression
{
public QueryOperator Operator { get; set; }
public abstract bool IsMatch(JToken t);
}
internal class CompositeExpression : QueryExpression
{
public List<QueryExpression> Expressions { get; set; }
public CompositeExpression()
{
Expressions = new List<QueryExpression>();
}
public override bool IsMatch(JToken t)
{
switch (Operator)
{
case QueryOperator.And:
foreach (QueryExpression e in Expressions)
{
if (!e.IsMatch(t))
return false;
}
return true;
case QueryOperator.Or:
foreach (QueryExpression e in Expressions)
{
if (e.IsMatch(t))
return true;
}
return false;
default:
throw new ArgumentOutOfRangeException();
}
}
}
internal class BooleanQueryExpression : QueryExpression
{
public List<PathFilter> Path { get; set; }
public QueryOperator Operator { get; set; }
public JValue Value { get; set; }
public override bool IsMatch(JToken t)
{
IEnumerable<JToken> pathResult = JPath.Evaluate(Path, t, false);
foreach (JToken r in pathResult)
{
JValue v = r as JValue;
switch (Operator)
{
case QueryOperator.Equals:
if (v != null && v.Equals(Value))
return true;
break;
case QueryOperator.NotEquals:
if (v != null && !v.Equals(Value))
return true;
break;
case QueryOperator.GreaterThan:
if (v != null && v.CompareTo(Value) > 0)
return true;
break;
case QueryOperator.GreaterThanOrEquals:
if (v != null && v.CompareTo(Value) >= 0)
return true;
break;
case QueryOperator.LessThan:
if (v != null && v.CompareTo(Value) < 0)
return true;
break;
case QueryOperator.LessThanOrEquals:
if (v != null && v.CompareTo(Value) <= 0)
return true;
break;
case QueryOperator.Exists:
return true;
default:
throw new ArgumentOutOfRangeException();
}
}
return false;
}
}
internal class QueryFilter : PathFilter
{
public List<QueryExpression> Expression { get; set; }
public QueryExpression Expression { get; set; }
public override IEnumerable<JToken> ExecuteFilter(IEnumerable<JToken> current, bool errorWhenNoMatch)
{
@@ -33,56 +117,10 @@ namespace Newtonsoft.Json.Linq.JsonPath
{
foreach (JToken v in t)
{
if (Evaluate(errorWhenNoMatch, v))
if (Expression.IsMatch(v))
yield return v;
}
}
}
private bool Evaluate(bool errorWhenNoMatch, JToken value)
{
foreach (QueryExpression expression in Expression)
{
IEnumerable<JToken> pathResult = JPath.Evaluate(expression.Path, value, errorWhenNoMatch);
foreach (JToken r in pathResult)
{
JValue v = r as JValue;
switch (expression.Operator)
{
case QueryOperator.Equals:
if (v != null && v.Equals(expression.Value))
return true;
break;
case QueryOperator.NotEquals:
if (v != null && !v.Equals(expression.Value))
return true;
break;
case QueryOperator.GreaterThan:
if (v != null && v.CompareTo(expression.Value) > 0)
return true;
break;
case QueryOperator.GreaterThanOrEquals:
if (v != null && v.CompareTo(expression.Value) >= 0)
return true;
break;
case QueryOperator.LessThan:
if (v != null && v.CompareTo(expression.Value) < 0)
return true;
break;
case QueryOperator.LessThanOrEquals:
if (v != null && v.CompareTo(expression.Value) <= 0)
return true;
break;
case QueryOperator.Exists:
return true;
default:
throw new ArgumentOutOfRangeException();
}
}
}
return false;
}
}
}
@@ -111,6 +111,7 @@
<Compile Include="Linq\JContainer.cs" />
<Compile Include="Linq\JEnumerable.cs" />
<Compile Include="Linq\JObject.cs" />
<Compile Include="Linq\JsonPath\AndFilter.cs" />
<Compile Include="Linq\JsonPath\ArrayIndexFilter.cs" />
<Compile Include="Linq\JsonPath\ArrayMultipleIndexFilter.cs" />
<Compile Include="Linq\JsonPath\ArraySliceFilter.cs" />
@@ -120,6 +121,7 @@
<Compile Include="Linq\JPropertyDescriptor.cs" />
<Compile Include="Linq\JPropertyKeyedCollection.cs" />
<Compile Include="Linq\JRaw.cs" />
<Compile Include="Linq\JsonPath\OrFilter.cs" />
<Compile Include="Linq\JsonPath\PathFilter.cs" />
<Compile Include="Linq\JsonPath\QueryFilter.cs" />
<Compile Include="Linq\JsonPath\ScanFilter.cs" />