From ac2d61947428a34eb6e48d26bfd22f7141c757ce Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 10 Sep 2016 16:54:29 +1200 Subject: [PATCH] -Added support for root object in JSONPath filters --- .../Linq/JsonPath/JPathExecuteTests.cs | 52 +++++++++++++++++++ .../Linq/JsonPath/JPathParseTests.cs | 10 ++++ .../Linq/JsonPath/QueryExpressionTests.cs | 28 +++++----- Src/Newtonsoft.Json/Linq/JToken.cs | 4 +- .../Linq/JsonPath/ArrayIndexFilter.cs | 2 +- .../Linq/JsonPath/ArrayMultipleIndexFilter.cs | 2 +- .../Linq/JsonPath/ArraySliceFilter.cs | 2 +- .../Linq/JsonPath/FieldFilter.cs | 2 +- .../Linq/JsonPath/FieldMultipleFilter.cs | 2 +- Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs | 18 ++++--- .../Linq/JsonPath/PathFilter.cs | 2 +- .../Linq/JsonPath/QueryExpression.cs | 12 ++--- .../Linq/JsonPath/QueryFilter.cs | 4 +- .../Linq/JsonPath/RootFilter.cs | 12 +++++ .../Linq/JsonPath/ScanFilter.cs | 14 ++--- .../Newtonsoft.Json.Net20.csproj | 1 + .../Newtonsoft.Json.Net35.csproj | 1 + .../Newtonsoft.Json.Net40.csproj | 1 + .../Newtonsoft.Json.Portable.csproj | 1 + .../Newtonsoft.Json.Portable40.csproj | 1 + Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 1 + 21 files changed, 128 insertions(+), 44 deletions(-) create mode 100644 Src/Newtonsoft.Json/Linq/JsonPath/RootFilter.cs diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs index 81c6cdc1..16723166 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs @@ -949,5 +949,57 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath result = a.SelectTokens("$.[?(@.value)]").ToList(); Assert.AreEqual(4, result.Count); } + + [Test] + public void RootInFilter() + { + string json = @"[ + { + ""store"" : { + ""book"" : [ + { + ""category"" : ""reference"", + ""author"" : ""Nigel Rees"", + ""title"" : ""Sayings of the Century"", + ""price"" : 8.95 + }, + { + ""category"" : ""fiction"", + ""author"" : ""Evelyn Waugh"", + ""title"" : ""Sword of Honour"", + ""price"" : 12.99 + }, + { + ""category"" : ""fiction"", + ""author"" : ""Herman Melville"", + ""title"" : ""Moby Dick"", + ""isbn"" : ""0-553-21311-3"", + ""price"" : 8.99 + }, + { + ""category"" : ""fiction"", + ""author"" : ""J. R. R. Tolkien"", + ""title"" : ""The Lord of the Rings"", + ""isbn"" : ""0-395-19395-8"", + ""price"" : 22.99 + } + ], + ""bicycle"" : { + ""color"" : ""red"", + ""price"" : 19.95 + } + }, + ""expensive"" : 10 + } +]"; + + JArray a = JArray.Parse(json); + + List result = a.SelectTokens("$.[?($.store.bicycle.price < 20)]").ToList(); + Assert.AreEqual(1, result.Count); + + result = a.SelectTokens("$.[?($.store.bicycle.price < 10)]").ToList(); + Assert.AreEqual(0, result.Count); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs index b5955605..e8d1d94a 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs @@ -419,6 +419,16 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath Assert.AreEqual(QueryOperator.Exists, orExpression.Expressions[1].Operator); } + [Test] + public void FilterWithRoot() + { + JPath path = new JPath("[?($.name>=12.1)]"); + BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(2, expressions.Path.Count); + Assert.IsInstanceOf(typeof(RootFilter), expressions.Path[0]); + Assert.IsInstanceOf(typeof(FieldFilter), expressions.Path[1]); + } + [Test] public void BadOr1() { diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs index 5b27247f..f64fe387 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs @@ -86,7 +86,7 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath { "LastName", "LastName!" } }; - Assert.IsTrue(compositeExpression.IsMatch(o1)); + Assert.IsTrue(compositeExpression.IsMatch(o1, o1)); JObject o2 = new JObject { @@ -94,14 +94,14 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath { "FirstName", "FirstName!" } }; - Assert.IsFalse(compositeExpression.IsMatch(o2)); + Assert.IsFalse(compositeExpression.IsMatch(o2, o2)); JObject o3 = new JObject { { "Title", "Title!" } }; - Assert.IsFalse(compositeExpression.IsMatch(o3)); + Assert.IsFalse(compositeExpression.IsMatch(o3, o3)); } [Test] @@ -144,7 +144,7 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath { "LastName", "LastName!" } }; - Assert.IsTrue(compositeExpression.IsMatch(o1)); + Assert.IsTrue(compositeExpression.IsMatch(o1, o1)); JObject o2 = new JObject { @@ -152,14 +152,14 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath { "FirstName", "FirstName!" } }; - Assert.IsTrue(compositeExpression.IsMatch(o2)); + Assert.IsTrue(compositeExpression.IsMatch(o2, o2)); JObject o3 = new JObject { { "Title", "Title!" } }; - Assert.IsFalse(compositeExpression.IsMatch(o3)); + Assert.IsFalse(compositeExpression.IsMatch(o3, o3)); } [Test] @@ -175,10 +175,10 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath } }; - Assert.IsTrue(e1.IsMatch(new JArray(1, 2, 3, 4, 5))); - Assert.IsTrue(e1.IsMatch(new JArray(2, 3, 4, 5))); - Assert.IsFalse(e1.IsMatch(new JArray(3, 4, 5))); - Assert.IsFalse(e1.IsMatch(new JArray(4, 5))); + Assert.IsTrue(e1.IsMatch(null, new JArray(1, 2, 3, 4, 5))); + Assert.IsTrue(e1.IsMatch(null, new JArray(2, 3, 4, 5))); + Assert.IsFalse(e1.IsMatch(null, new JArray(3, 4, 5))); + Assert.IsFalse(e1.IsMatch(null, new JArray(4, 5))); BooleanQueryExpression e2 = new BooleanQueryExpression { @@ -190,10 +190,10 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath } }; - Assert.IsTrue(e2.IsMatch(new JArray(1, 2, 3, 4, 5))); - Assert.IsTrue(e2.IsMatch(new JArray(2, 3, 4, 5))); - Assert.IsTrue(e2.IsMatch(new JArray(3, 4, 5))); - Assert.IsFalse(e2.IsMatch(new JArray(4, 5))); + Assert.IsTrue(e2.IsMatch(null, new JArray(1, 2, 3, 4, 5))); + Assert.IsTrue(e2.IsMatch(null, new JArray(2, 3, 4, 5))); + Assert.IsTrue(e2.IsMatch(null, new JArray(3, 4, 5))); + Assert.IsFalse(e2.IsMatch(null, new JArray(4, 5))); } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JToken.cs b/Src/Newtonsoft.Json/Linq/JToken.cs index 8bcbf83d..fe5aa977 100644 --- a/Src/Newtonsoft.Json/Linq/JToken.cs +++ b/Src/Newtonsoft.Json/Linq/JToken.cs @@ -2305,7 +2305,7 @@ namespace Newtonsoft.Json.Linq JPath p = new JPath(path); JToken token = null; - foreach (JToken t in p.Evaluate(this, errorWhenNoMatch)) + foreach (JToken t in p.Evaluate(this, this, errorWhenNoMatch)) { if (token != null) { @@ -2341,7 +2341,7 @@ namespace Newtonsoft.Json.Linq public IEnumerable SelectTokens(string path, bool errorWhenNoMatch) { JPath p = new JPath(path); - return p.Evaluate(this, errorWhenNoMatch); + return p.Evaluate(this, this, errorWhenNoMatch); } #if !(NET35 || NET20 || PORTABLE40) diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ArrayIndexFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ArrayIndexFilter.cs index 05537a43..c6cb1e88 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/ArrayIndexFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ArrayIndexFilter.cs @@ -8,7 +8,7 @@ namespace Newtonsoft.Json.Linq.JsonPath { public int? Index { get; set; } - public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + public override IEnumerable ExecuteFilter(JToken root, IEnumerable current, bool errorWhenNoMatch) { foreach (JToken t in current) { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ArrayMultipleIndexFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ArrayMultipleIndexFilter.cs index 81d64e36..170bfc47 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/ArrayMultipleIndexFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ArrayMultipleIndexFilter.cs @@ -6,7 +6,7 @@ namespace Newtonsoft.Json.Linq.JsonPath { public List Indexes { get; set; } - public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + public override IEnumerable ExecuteFilter(JToken root, IEnumerable current, bool errorWhenNoMatch) { foreach (JToken t in current) { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs index 65139d21..2cc34bb8 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs @@ -11,7 +11,7 @@ namespace Newtonsoft.Json.Linq.JsonPath public int? End { get; set; } public int? Step { get; set; } - public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + public override IEnumerable ExecuteFilter(JToken root, IEnumerable current, bool errorWhenNoMatch) { if (Step == 0) { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs index e349466b..fdddbe89 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs @@ -8,7 +8,7 @@ namespace Newtonsoft.Json.Linq.JsonPath { public string Name { get; set; } - public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + public override IEnumerable ExecuteFilter(JToken root, IEnumerable current, bool errorWhenNoMatch) { foreach (JToken t in current) { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/FieldMultipleFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/FieldMultipleFilter.cs index 940818a3..43bffbcc 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/FieldMultipleFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/FieldMultipleFilter.cs @@ -13,7 +13,7 @@ namespace Newtonsoft.Json.Linq.JsonPath { public List Names { get; set; } - public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + public override IEnumerable ExecuteFilter(JToken root, IEnumerable current, bool errorWhenNoMatch) { foreach (JToken t in current) { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs index a8346a14..edf582ae 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs @@ -429,15 +429,19 @@ namespace Newtonsoft.Json.Linq.JsonPath { EatWhitespace(); - if (_expression[_currentIndex] != '@') + List expressionPath = new List(); + + if (_expression[_currentIndex] == '$') + { + expressionPath.Add(new RootFilter()); + } + else if (_expression[_currentIndex] != '@') { throw new JsonException("Unexpected character while parsing path query: " + _expression[_currentIndex]); } _currentIndex++; - List expressionPath = new List(); - if (ParsePath(expressionPath, _currentIndex, true)) { throw new JsonException("Path ended with open query."); @@ -758,17 +762,17 @@ namespace Newtonsoft.Json.Linq.JsonPath } } - internal IEnumerable Evaluate(JToken t, bool errorWhenNoMatch) + internal IEnumerable Evaluate(JToken root, JToken t, bool errorWhenNoMatch) { - return Evaluate(Filters, t, errorWhenNoMatch); + return Evaluate(Filters, root, t, errorWhenNoMatch); } - internal static IEnumerable Evaluate(List filters, JToken t, bool errorWhenNoMatch) + internal static IEnumerable Evaluate(List filters, JToken root, JToken t, bool errorWhenNoMatch) { IEnumerable current = new[] { t }; foreach (PathFilter filter in filters) { - current = filter.ExecuteFilter(current, errorWhenNoMatch); + current = filter.ExecuteFilter(root, current, errorWhenNoMatch); } return current; diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/PathFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/PathFilter.cs index c1bce170..3de2afaa 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/PathFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/PathFilter.cs @@ -6,7 +6,7 @@ namespace Newtonsoft.Json.Linq.JsonPath { internal abstract class PathFilter { - public abstract IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch); + public abstract IEnumerable ExecuteFilter(JToken root, IEnumerable current, bool errorWhenNoMatch); protected static JToken GetTokenIndex(JToken t, bool errorWhenNoMatch, int index) { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs index ee80d779..bf6515bd 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs @@ -24,7 +24,7 @@ namespace Newtonsoft.Json.Linq.JsonPath { public QueryOperator Operator { get; set; } - public abstract bool IsMatch(JToken t); + public abstract bool IsMatch(JToken root, JToken t); } internal class CompositeExpression : QueryExpression @@ -36,14 +36,14 @@ namespace Newtonsoft.Json.Linq.JsonPath Expressions = new List(); } - public override bool IsMatch(JToken t) + public override bool IsMatch(JToken root, JToken t) { switch (Operator) { case QueryOperator.And: foreach (QueryExpression e in Expressions) { - if (!e.IsMatch(t)) + if (!e.IsMatch(root, t)) { return false; } @@ -52,7 +52,7 @@ namespace Newtonsoft.Json.Linq.JsonPath case QueryOperator.Or: foreach (QueryExpression e in Expressions) { - if (e.IsMatch(t)) + if (e.IsMatch(root, t)) { return true; } @@ -69,9 +69,9 @@ namespace Newtonsoft.Json.Linq.JsonPath public List Path { get; set; } public JValue Value { get; set; } - public override bool IsMatch(JToken t) + public override bool IsMatch(JToken root, JToken t) { - IEnumerable pathResult = JPath.Evaluate(Path, t, false); + IEnumerable pathResult = JPath.Evaluate(Path, root, t, false); foreach (JToken r in pathResult) { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs index 9dab576f..775acd80 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs @@ -7,13 +7,13 @@ namespace Newtonsoft.Json.Linq.JsonPath { public QueryExpression Expression { get; set; } - public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + public override IEnumerable ExecuteFilter(JToken root, IEnumerable current, bool errorWhenNoMatch) { foreach (JToken t in current) { foreach (JToken v in t) { - if (Expression.IsMatch(v)) + if (Expression.IsMatch(root, v)) { yield return v; } diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/RootFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/RootFilter.cs new file mode 100644 index 00000000..a91c2aa5 --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonPath/RootFilter.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Newtonsoft.Json.Linq.JsonPath +{ + internal class RootFilter : PathFilter + { + public override IEnumerable ExecuteFilter(JToken root, IEnumerable current, bool errorWhenNoMatch) + { + return root; + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ScanFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ScanFilter.cs index 930adc49..2ae9f2cb 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/ScanFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ScanFilter.cs @@ -6,17 +6,17 @@ namespace Newtonsoft.Json.Linq.JsonPath { public string Name { get; set; } - public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + public override IEnumerable ExecuteFilter(JToken root, IEnumerable current, bool errorWhenNoMatch) { - foreach (JToken root in current) + foreach (JToken c in current) { if (Name == null) { - yield return root; + yield return c; } - JToken value = root; - JToken container = root; + JToken value = c; + JToken container = c; while (true) { @@ -26,12 +26,12 @@ namespace Newtonsoft.Json.Linq.JsonPath } else { - while (value != null && value != root && value == value.Parent.Last) + while (value != null && value != c && value == value.Parent.Last) { value = value.Parent; } - if (value == null || value == root) + if (value == null || value == c) { break; } diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj index 436a3683..45c90c96 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -123,6 +123,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj index 697a958c..064c86a1 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj @@ -137,6 +137,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj index 2e41fe86..a80578fe 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj @@ -137,6 +137,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj index 56f6b0ce..a962cd15 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj @@ -128,6 +128,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj index 55c3494a..17065730 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj @@ -133,6 +133,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 1055878b..caab3fec 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -131,6 +131,7 @@ +