From e3bf0a620a2af0b07b2858731a253d7871453562 Mon Sep 17 00:00:00 2001 From: Marc Pynaert Date: Sun, 14 Apr 2019 13:22:29 +0200 Subject: [PATCH 1/2] Add support of new operators ANYOF and NONEOF This reverts commit f3ef9d0992d8bf141cf481af78c30b97190f9033. --- README.md | 44 +++++------ .../java/com/jayway/jsonpath/Criteria.java | 50 +++++++++++++ .../internal/filter/EvaluatorFactory.java | 75 +++++++++++++++++++ .../internal/filter/RelationalOperator.java | 4 +- .../com/jayway/jsonpath/FilterParseTest.java | 20 ++++- .../java/com/jayway/jsonpath/FilterTest.java | 31 +++++++- 6 files changed, 200 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 90826ee2..1081c580 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,13 @@ Functions Functions can be invoked at the tail end of a path - the input to a function is the output of the path expression. The function output is dictated by the function itself. -| Function | Description | Output | -| :------------------------ | :----------------------------------------------------------------- |-----------| -| min() | Provides the min value of an array of numbers | Double | -| max() | Provides the max value of an array of numbers | Double | -| avg() | Provides the average value of an array of numbers | Double | -| stddev() | Provides the standard deviation value of an array of numbers | Double | -| length() | Provides the length of an array | Integer | +| Function | Description | Output | +| :------------------------ | :------------------------------------------------------------------ |-----------| +| min() | Provides the min value of an array of numbers | Double | +| max() | Provides the max value of an array of numbers | Double | +| avg() | Provides the average value of an array of numbers | Double | +| stddev() | Provides the standard deviation value of an array of numbers | Double | +| length() | Provides the length of an array | Integer | Filter Operators @@ -91,20 +91,22 @@ Filter Operators Filters are logical expressions used to filter arrays. A typical filter would be `[?(@.age > 18)]` where `@` represents the current item being processed. More complex filters can be created with logical operators `&&` and `||`. String literals must be enclosed by single or double quotes (`[?(@.color == 'blue')]` or `[?(@.color == "blue")]`). -| Operator | Description | -| :----------------------- | :---------------------------------------------------------------- | -| == | left is equal to right (note that 1 is not equal to '1') | -| != | left is not equal to right | -| < | left is less than right | -| <= | left is less or equal to right | -| > | left is greater than right | -| >= | left is greater than or equal to right | -| =~ | left matches regular expression [?(@.name =~ /foo.*?/i)] | -| in | left exists in right [?(@.size in ['S', 'M'])] | -| nin | left does not exists in right | -| subsetof | left is a subset of right [?(@.sizes subsetof ['S', 'M', 'L'])] | -| size | size of left (array or string) should match right | -| empty | left (array or string) should be empty | +| Operator | Description | +| :----------------------- | :-------------------------------------------------------------------- | +| == | left is equal to right (note that 1 is not equal to '1') | +| != | left is not equal to right | +| < | left is less than right | +| <= | left is less or equal to right | +| > | left is greater than right | +| >= | left is greater than or equal to right | +| =~ | left matches regular expression [?(@.name =~ /foo.*?/i)] | +| in | left exists in right [?(@.size in ['S', 'M'])] | +| nin | left does not exists in right | +| subsetof | left is a subset of right [?(@.sizes subsetof ['S', 'M', 'L'])] | +| anyof | left has an intersection with right [?(@.sizes anyof ['M', 'L'])] | +| noneof | left has no intersection with right [?(@.sizes noneof ['M', 'L'])] | +| size | size of left (array or string) should match right | +| empty | left (array or string) should be empty | Path Examples diff --git a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java index ea32460e..5155b486 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java @@ -296,6 +296,56 @@ public class Criteria implements Predicate { return this; } + /** + * The anyof operator selects objects for which the specified field is + * an array that contain at least an element in the specified array. + * + * @param o the values to match against + * @return the criteria + */ + public Criteria anyof(Object... o) { + return subsetof(Arrays.asList(o)); + } + + /** + * The anyof operator selects objects for which the specified field is + * an array that contain at least an element in the specified array. + * + * @param c the values to match against + * @return the criteria + */ + public Criteria anyof(Collection c) { + notNull(c, "collection can not be null"); + this.criteriaType = RelationalOperator.ANYOF; + this.right = new ValueNode.ValueListNode(c); + return this; + } + + /** + * The noneof operator selects objects for which the specified field is + * an array that does not contain any of the elements of the specified array. + * + * @param o the values to match against + * @return the criteria + */ + public Criteria noneof(Object... o) { + return subsetof(Arrays.asList(o)); + } + + /** + * The noneof operator selects objects for which the specified field is + * an array that does not contain any of the elements of the specified array. + * + * @param c the values to match against + * @return the criteria + */ + public Criteria noneof(Collection c) { + notNull(c, "collection can not be null"); + this.criteriaType = RelationalOperator.NONEOF; + this.right = new ValueNode.ValueListNode(c); + return this; + } + /** * The all operator is similar to $in, but instead of matching any value * in the specified array all values in the array must be matched. diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java index 0669e7e8..a006e638 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java @@ -31,6 +31,8 @@ public class EvaluatorFactory { evaluators.put(RelationalOperator.MATCHES, new PredicateMatchEvaluator()); evaluators.put(RelationalOperator.TYPE, new TypeEvaluator()); evaluators.put(RelationalOperator.SUBSETOF, new SubsetOfEvaluator()); + evaluators.put(RelationalOperator.ANYOF, new AnyOfEvaluator()); + evaluators.put(RelationalOperator.NONEOF, new NoneOfEvaluator()); } public static Evaluator createEvaluator(RelationalOperator operator){ @@ -296,4 +298,77 @@ public class EvaluatorFactory { } } + private static class AnyOfEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueNode.ValueListNode rightValueListNode; + if (right.isJsonNode()) { + ValueNode vn = right.asJsonNode().asValueListNode(ctx); + if (vn.isUndefinedNode()) { + return false; + } else { + rightValueListNode = vn.asValueListNode(); + } + } else { + rightValueListNode = right.asValueListNode(); + } + ValueNode.ValueListNode leftValueListNode; + if (left.isJsonNode()) { + ValueNode vn = left.asJsonNode().asValueListNode(ctx); + if (vn.isUndefinedNode()) { + return false; + } else { + leftValueListNode = vn.asValueListNode(); + } + } else { + leftValueListNode = left.asValueListNode(); + } + + for (ValueNode leftValueNode : leftValueListNode) { + for (ValueNode rightValueNode : rightValueListNode) { + if (leftValueNode.equals(rightValueNode)) { + return true; + } + } + } + return false; + } + } + + private static class NoneOfEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueNode.ValueListNode rightValueListNode; + if (right.isJsonNode()) { + ValueNode vn = right.asJsonNode().asValueListNode(ctx); + if (vn.isUndefinedNode()) { + return false; + } else { + rightValueListNode = vn.asValueListNode(); + } + } else { + rightValueListNode = right.asValueListNode(); + } + ValueNode.ValueListNode leftValueListNode; + if (left.isJsonNode()) { + ValueNode vn = left.asJsonNode().asValueListNode(ctx); + if (vn.isUndefinedNode()) { + return false; + } else { + leftValueListNode = vn.asValueListNode(); + } + } else { + leftValueListNode = left.asValueListNode(); + } + + for (ValueNode leftValueNode : leftValueListNode) { + for (ValueNode rightValueNode : rightValueListNode) { + if (leftValueNode.equals(rightValueNode)) { + return false; + } + } + } + return true; + } + } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java index 830cc3bb..84e2aed0 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java @@ -30,7 +30,9 @@ public enum RelationalOperator { TYPE("TYPE"), MATCHES("MATCHES"), EMPTY("EMPTY"), - SUBSETOF("SUBSETOF"); + SUBSETOF("SUBSETOF"), + ANYOF("ANYOF"), + NONEOF("NONEOF"); private final String operatorString; diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java index 948ca75f..37e37f79 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java @@ -1,9 +1,9 @@ package com.jayway.jsonpath; -import java.util.Collections; import org.assertj.core.api.Assertions; import org.junit.Test; +import java.util.Collections; import java.util.regex.Pattern; import static com.jayway.jsonpath.Criteria.where; @@ -152,6 +152,24 @@ public class FilterParseTest { assertThat(filter).isEqualTo(parsed); } + @Test + public void a_anyof_filter_can_be_serialized() { + + String filter = filter(where("a").anyof(Collections.emptyList())).toString(); + String parsed = parse("[?(@['a'] ANYOF [])]").toString(); + + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_noneof_filter_can_be_serialized() { + + String filter = filter(where("a").noneof(Collections.emptyList())).toString(); + String parsed = parse("[?(@['a'] NONEOF [])]").toString(); + + assertThat(filter).isEqualTo(parsed); + } + @Test public void a_exists_filter_can_be_serialized() { diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java index 366a8db9..545f29c6 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java @@ -1,6 +1,5 @@ package com.jayway.jsonpath; -import java.util.ArrayList; import org.assertj.core.util.Lists; import org.junit.Test; @@ -379,6 +378,36 @@ public class FilterTest extends BaseTest { assertThat(filter(where("string-arr").subsetof(list)).apply(createPredicateContext(json))).isEqualTo(false); } + //---------------------------------------------------------------------------- + // + // ANYOF + // + //---------------------------------------------------------------------------- + @Test + public void array_anyof_evals() { + List list = Lists.newArrayList("a", "z"); + assertThat(filter(where("string-arr").anyof(list)).apply(createPredicateContext(json))).isEqualTo(true); + list = Lists.newArrayList("z", "b", "a"); + assertThat(filter(where("string-arr").anyof(list)).apply(createPredicateContext(json))).isEqualTo(true); + list = Lists.newArrayList("x", "y", "z"); + assertThat(filter(where("string-arr").anyof(list)).apply(createPredicateContext(json))).isEqualTo(false); + } + + //---------------------------------------------------------------------------- + // + // NONEOF + // + //---------------------------------------------------------------------------- + @Test + public void array_noneof_evals() { + List list = Lists.newArrayList("a", "z"); + assertThat(filter(where("string-arr").noneof(list)).apply(createPredicateContext(json))).isEqualTo(false); + list = Lists.newArrayList("z", "b", "a"); + assertThat(filter(where("string-arr").noneof(list)).apply(createPredicateContext(json))).isEqualTo(false); + list = Lists.newArrayList("x", "y", "z"); + assertThat(filter(where("string-arr").noneof(list)).apply(createPredicateContext(json))).isEqualTo(true); + } + //---------------------------------------------------------------------------- // // EXISTS From 39b489339c0c436a69177030f64b7f5691a2fea8 Mon Sep 17 00:00:00 2001 From: Marc PYNAERT Date: Sun, 14 Apr 2019 13:29:48 +0200 Subject: [PATCH 2/2] Fix compilation errors (ValueNodes.ValueListNode => ValueListNode) --- .../src/main/java/com/jayway/jsonpath/Criteria.java | 8 ++++---- .../jsonpath/internal/filter/EvaluatorFactory.java | 12 +++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) mode change 100644 => 100755 json-path/src/main/java/com/jayway/jsonpath/Criteria.java mode change 100644 => 100755 json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java diff --git a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java old mode 100644 new mode 100755 index 5155b486..7f2706df --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java @@ -19,6 +19,7 @@ import com.jayway.jsonpath.internal.Utils; import com.jayway.jsonpath.internal.filter.RelationalExpressionNode; import com.jayway.jsonpath.internal.filter.RelationalOperator; import com.jayway.jsonpath.internal.filter.ValueNode; +import com.jayway.jsonpath.internal.filter.ValueNodes; import java.util.ArrayList; import java.util.Arrays; @@ -28,9 +29,8 @@ import java.util.List; import java.util.regex.Pattern; import static com.jayway.jsonpath.internal.Utils.notNull; -import com.jayway.jsonpath.internal.filter.ValueNodes; -import static com.jayway.jsonpath.internal.filter.ValueNodes.ValueListNode; import static com.jayway.jsonpath.internal.filter.ValueNodes.PredicateNode; +import static com.jayway.jsonpath.internal.filter.ValueNodes.ValueListNode; /** * @@ -317,7 +317,7 @@ public class Criteria implements Predicate { public Criteria anyof(Collection c) { notNull(c, "collection can not be null"); this.criteriaType = RelationalOperator.ANYOF; - this.right = new ValueNode.ValueListNode(c); + this.right = new ValueListNode(c); return this; } @@ -342,7 +342,7 @@ public class Criteria implements Predicate { public Criteria noneof(Collection c) { notNull(c, "collection can not be null"); this.criteriaType = RelationalOperator.NONEOF; - this.right = new ValueNode.ValueListNode(c); + this.right = new ValueListNode(c); return this; } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java old mode 100644 new mode 100755 index a006e638..cfbfdff7 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java @@ -2,11 +2,13 @@ package com.jayway.jsonpath.internal.filter; import com.jayway.jsonpath.JsonPathException; import com.jayway.jsonpath.Predicate; -import static com.jayway.jsonpath.internal.filter.ValueNodes.*; import java.util.HashMap; import java.util.Map; +import static com.jayway.jsonpath.internal.filter.ValueNodes.PatternNode; +import static com.jayway.jsonpath.internal.filter.ValueNodes.ValueListNode; + public class EvaluatorFactory { private static final Map evaluators = new HashMap(); @@ -301,7 +303,7 @@ public class EvaluatorFactory { private static class AnyOfEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - ValueNode.ValueListNode rightValueListNode; + ValueListNode rightValueListNode; if (right.isJsonNode()) { ValueNode vn = right.asJsonNode().asValueListNode(ctx); if (vn.isUndefinedNode()) { @@ -312,7 +314,7 @@ public class EvaluatorFactory { } else { rightValueListNode = right.asValueListNode(); } - ValueNode.ValueListNode leftValueListNode; + ValueListNode leftValueListNode; if (left.isJsonNode()) { ValueNode vn = left.asJsonNode().asValueListNode(ctx); if (vn.isUndefinedNode()) { @@ -338,7 +340,7 @@ public class EvaluatorFactory { private static class NoneOfEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - ValueNode.ValueListNode rightValueListNode; + ValueListNode rightValueListNode; if (right.isJsonNode()) { ValueNode vn = right.asJsonNode().asValueListNode(ctx); if (vn.isUndefinedNode()) { @@ -349,7 +351,7 @@ public class EvaluatorFactory { } else { rightValueListNode = right.asValueListNode(); } - ValueNode.ValueListNode leftValueListNode; + ValueListNode leftValueListNode; if (left.isJsonNode()) { ValueNode vn = left.asJsonNode().asValueListNode(ctx); if (vn.isUndefinedNode()) {