From 865b2076c283dd74dbb9a6e6001628458d88847e Mon Sep 17 00:00:00 2001 From: Minh Nguyen Date: Thu, 20 Jul 2017 18:03:55 +1000 Subject: [PATCH] feature: implement new regex equals operator for JsonPath Query expression. Example: Blah[?( @.name =~'/hi/i' )] --- .../Linq/JsonPath/JPathParseTests.cs | 11 ++++ .../Linq/JsonPath/QueryExpressionTests.cs | 64 +++++++++++++++++++ Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs | 6 ++ .../Linq/JsonPath/QueryExpression.cs | 39 ++++++++++- 4 files changed, 119 insertions(+), 1 deletion(-) diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs index d29ec4ac..21e4780a 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs @@ -314,6 +314,17 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath Assert.AreEqual("h\\i", (string)(JToken)expressions.Right); } + [Test] + public void SinglePropertyAndFilterWithRegex() + { + JPath path = new JPath("Blah[ ?( @.name=~'/hi/i' ) ]"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[1]).Expression; + Assert.AreEqual(QueryOperator.RegExEquals, expressions.Operator); + Assert.AreEqual("/hi/i", (string)(JToken)expressions.Right); + } + [Test] public void SinglePropertyAndFilterWithUnknownEscape() { diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs index 1ca720b0..a456daa9 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs @@ -157,6 +157,70 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath Assert.IsFalse(compositeExpression.IsMatch(o3, o3)); } + + [Test] + public void BooleanExpressionTest_RegExEqualsOperator() + { + BooleanQueryExpression e1 = new BooleanQueryExpression + { + Operator = QueryOperator.RegExEquals, + Right = new JValue("/foo.*d/"), + Left = new List + { + new ArrayIndexFilter() + } + }; + + Assert.IsTrue(e1.IsMatch(null, new JArray("food"))); + Assert.IsTrue(e1.IsMatch(null, new JArray("fooood and drink"))); + Assert.IsFalse(e1.IsMatch(null, new JArray("FOOD"))); + Assert.IsFalse(e1.IsMatch(null, new JArray("foo", "foog", "good"))); + + BooleanQueryExpression e2 = new BooleanQueryExpression + { + Operator = QueryOperator.RegExEquals, + Right = new JValue("/Foo.*d/i"), + Left = new List + { + new ArrayIndexFilter() + } + }; + + Assert.IsTrue(e2.IsMatch(null, new JArray("food"))); + Assert.IsTrue(e2.IsMatch(null, new JArray("fooood and drink"))); + Assert.IsTrue(e2.IsMatch(null, new JArray("FOOD"))); + Assert.IsFalse(e2.IsMatch(null, new JArray("foo", "foog", "good"))); + } + + [Test] + public void BooleanExpressionTest_RegExEqualsOperator_CornerCase() + { + BooleanQueryExpression e1 = new BooleanQueryExpression + { + Operator = QueryOperator.RegExEquals, + Right = new JValue("/// comment/"), + Left = new List + { + new ArrayIndexFilter() + } + }; + + Assert.IsTrue(e1.IsMatch(null, new JArray("// comment"))); + Assert.IsFalse(e1.IsMatch(null, new JArray("//comment", "/ comment"))); + + BooleanQueryExpression e2 = new BooleanQueryExpression + { + Operator = QueryOperator.RegExEquals, + Right = new JValue("/.*/i"), + Left = new List + { + new ArrayIndexFilter() + } + }; + + Assert.IsTrue(e2.IsMatch(null, new JArray("Test", ""))); + Assert.IsFalse(e2.IsMatch(null, new JArray("Test"))); + } [Test] public void BooleanExpressionTest() diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs index 1f15f2ef..ae99b5dc 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs @@ -728,6 +728,12 @@ namespace Newtonsoft.Json.Linq.JsonPath { return QueryOperator.Equals; } + + if (Match("=~")) + { + return QueryOperator.RegExEquals; + } + if (Match("!=") || Match("<>")) { return QueryOperator.NotEquals; diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs index 245bffe7..6f617c04 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Text.RegularExpressions; #if !HAVE_LINQ using Newtonsoft.Json.Utilities.LinqBridge; #else @@ -22,7 +23,8 @@ namespace Newtonsoft.Json.Linq.JsonPath GreaterThan = 6, GreaterThanOrEquals = 7, And = 8, - Or = 9 + Or = 9, + RegExEquals = 10 } internal abstract class QueryExpression @@ -131,6 +133,12 @@ namespace Newtonsoft.Json.Linq.JsonPath { switch (Operator) { + case QueryOperator.RegExEquals: + if (RegExEquals(leftValue, rightValue)) + { + return true; + } + break; case QueryOperator.Equals: if (EqualsWithStringCoercion(leftValue, rightValue)) { @@ -186,6 +194,35 @@ namespace Newtonsoft.Json.Linq.JsonPath return false; } + private bool RegExEquals(JValue value, JValue queryValue) + { + if (queryValue.Type != JTokenType.String || value.Type != JTokenType.String) + { + return false; + } + + var regExArray = ((string)queryValue.Value).Split('/'); + if (regExArray.Length < 3) + { + throw new Exception("Syntax error when using regex compare"); + } + + var trimFirstAndLastMember = regExArray.Skip(1).Take(regExArray.Length - 2).ToArray(); + var regExQuery = string.Join("/", trimFirstAndLastMember); + + if (regExArray.Last() == string.Empty) + { + return Regex.IsMatch((string)value.Value, regExQuery); + } + + if (regExArray.Last() == "i") + { + return Regex.IsMatch((string)value.Value, regExQuery, RegexOptions.IgnoreCase); + } + + throw new Exception("Syntax error when using regex compare"); + } + private bool EqualsWithStringCoercion(JValue value, JValue queryValue) { if (value.Equals(queryValue))