From 32adc12c9c14842fac77e86015702814dbe060a8 Mon Sep 17 00:00:00 2001 From: Ari Fogel Date: Tue, 28 Mar 2017 15:03:05 -0700 Subject: [PATCH 1/3] implement subset filter operator, update readme --- README.md | 1 + .../java/com/jayway/jsonpath/Criteria.java | 27 ++++++++++++++++ .../internal/filter/EvaluatorFactory.java | 31 +++++++++++++++++++ .../internal/filter/RelationalOperator.java | 3 +- .../jsonpath/internal/filter/ValueNode.java | 9 ++++++ 5 files changed, 70 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0109c7ba..bf705064 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ Filters are logical expressions used to filter arrays. A typical filter would be | =~ | left matches regular expression [?(@.name =~ /foo.*?/i)] | | in | left exists in right [?(@.size in ['S', 'M'])] | | nin | left does not exists in right | +| subset | left is a subset of right [?(@.sizes subset ['S', 'M', 'L'])] | | size | size of left (array or string) should match right | | empty | left (array or string) should be empty | 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 30e1eda1..789b2a83 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java @@ -267,6 +267,33 @@ public class Criteria implements Predicate { return this; } + /** + * The subset operator selects objects for which the specified field is + * an array whose elements comprise a subset of the set comprised by the elements of + * the specified array. + * + * @param o the values to match against + * @return the criteria + */ + public Criteria subset(Object... o) { + return subset(Arrays.asList(o)); + } + + /** + * The subset operator selects objects for which the specified field is + * an array whose elements comprise a subset of the set comprised by the elements of + * the specified array. + * + * @param c the values to match against + * @return the criteria + */ + public Criteria subset(Collection c) { + notNull(c, "collection can not be null"); + this.criteriaType = RelationalOperator.SUBSET; + 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 6831920c..2491a159 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 @@ -29,6 +29,7 @@ public class EvaluatorFactory { evaluators.put(RelationalOperator.CONTAINS, new ContainsEvaluator()); evaluators.put(RelationalOperator.MATCHES, new PredicateMatchEvaluator()); evaluators.put(RelationalOperator.TYPE, new TypeEvaluator()); + evaluators.put(RelationalOperator.SUBSET, new SubsetEvaluator()); } public static Evaluator createEvaluator(RelationalOperator operator){ @@ -264,4 +265,34 @@ public class EvaluatorFactory { return input; } } + + private static class SubsetEvaluator 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(); + } + return leftValueListNode.subset(rightValueListNode); + } + } + } 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 4575f60b..d8ff484c 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 @@ -29,7 +29,8 @@ public enum RelationalOperator { EXISTS("EXISTS"), TYPE("TYPE"), MATCHES("MATCHES"), - EMPTY("EMPTY"); + EMPTY("EMPTY"), + SUBSET("SUBSET"); private final String operatorString; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java index 08d257c4..9aac34a4 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java @@ -714,6 +714,15 @@ public abstract class ValueNode { return nodes.contains(node); } + public boolean subset(ValueListNode right) { + for (ValueNode leftNode : nodes) { + if (!right.nodes.contains(leftNode)) { + return false; + } + } + return true; + } + public List getNodes() { return Collections.unmodifiableList(nodes); } From c9527bf89e5c314b19b9c6c16d3bcab8e0926b08 Mon Sep 17 00:00:00 2001 From: Daniel Halperin Date: Wed, 28 Jun 2017 10:15:54 -0700 Subject: [PATCH 2/3] Rename subset to subsetof --- README.md | 2 +- .../src/main/java/com/jayway/jsonpath/Criteria.java | 12 ++++++------ .../jsonpath/internal/filter/EvaluatorFactory.java | 6 +++--- .../jsonpath/internal/filter/RelationalOperator.java | 2 +- .../jayway/jsonpath/internal/filter/ValueNode.java | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bf705064..5efe798c 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Filters are logical expressions used to filter arrays. A typical filter would be | =~ | left matches regular expression [?(@.name =~ /foo.*?/i)] | | in | left exists in right [?(@.size in ['S', 'M'])] | | nin | left does not exists in right | -| subset | left is a subset of right [?(@.sizes subset ['S', 'M', 'L'])] | +| 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 | 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 789b2a83..92f9b2bc 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java @@ -268,28 +268,28 @@ public class Criteria implements Predicate { } /** - * The subset operator selects objects for which the specified field is + * The subsetof operator selects objects for which the specified field is * an array whose elements comprise a subset of the set comprised by the elements of * the specified array. * * @param o the values to match against * @return the criteria */ - public Criteria subset(Object... o) { - return subset(Arrays.asList(o)); + public Criteria subsetof(Object... o) { + return subsetof(Arrays.asList(o)); } /** - * The subset operator selects objects for which the specified field is + * The subsetof operator selects objects for which the specified field is * an array whose elements comprise a subset of the set comprised by the elements of * the specified array. * * @param c the values to match against * @return the criteria */ - public Criteria subset(Collection c) { + public Criteria subsetof(Collection c) { notNull(c, "collection can not be null"); - this.criteriaType = RelationalOperator.SUBSET; + this.criteriaType = RelationalOperator.SUBSETOF; this.right = new ValueNode.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 index 2491a159..ac47274a 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 @@ -29,7 +29,7 @@ public class EvaluatorFactory { evaluators.put(RelationalOperator.CONTAINS, new ContainsEvaluator()); evaluators.put(RelationalOperator.MATCHES, new PredicateMatchEvaluator()); evaluators.put(RelationalOperator.TYPE, new TypeEvaluator()); - evaluators.put(RelationalOperator.SUBSET, new SubsetEvaluator()); + evaluators.put(RelationalOperator.SUBSETOF, new SubsetOfEvaluator()); } public static Evaluator createEvaluator(RelationalOperator operator){ @@ -266,7 +266,7 @@ public class EvaluatorFactory { } } - private static class SubsetEvaluator implements Evaluator { + private static class SubsetOfEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { ValueNode.ValueListNode rightValueListNode; @@ -291,7 +291,7 @@ public class EvaluatorFactory { } else { leftValueListNode = left.asValueListNode(); } - return leftValueListNode.subset(rightValueListNode); + return leftValueListNode.subsetof(rightValueListNode); } } 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 d8ff484c..830cc3bb 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,7 @@ public enum RelationalOperator { TYPE("TYPE"), MATCHES("MATCHES"), EMPTY("EMPTY"), - SUBSET("SUBSET"); + SUBSETOF("SUBSETOF"); private final String operatorString; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java index 9aac34a4..308a453a 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java @@ -714,7 +714,7 @@ public abstract class ValueNode { return nodes.contains(node); } - public boolean subset(ValueListNode right) { + public boolean subsetof(ValueListNode right) { for (ValueNode leftNode : nodes) { if (!right.nodes.contains(leftNode)) { return false; From 7a6fa59a8586a29c6d9688df5fc3b6720ed3a28d Mon Sep 17 00:00:00 2001 From: Daniel Halperin Date: Wed, 28 Jun 2017 10:29:20 -0700 Subject: [PATCH 3/3] Add tests for subsetof --- .../com/jayway/jsonpath/FilterParseTest.java | 10 ++++++++++ .../java/com/jayway/jsonpath/FilterTest.java | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+) 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 a073d9b1..948ca75f 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java @@ -1,5 +1,6 @@ package com.jayway.jsonpath; +import java.util.Collections; import org.assertj.core.api.Assertions; import org.junit.Test; @@ -142,6 +143,15 @@ public class FilterParseTest { assertThat(filter).isEqualTo(parsed); } + @Test + public void a_subsetof_filter_can_be_serialized() { + + String filter = filter(where("a").subsetof(Collections.emptyList())).toString(); + String parsed = parse("[?(@['a'] SUBSETOF [])]").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 a9c02e76..e786ae83 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java @@ -1,5 +1,7 @@ package com.jayway.jsonpath; +import java.util.ArrayList; +import org.assertj.core.util.Lists; import org.junit.Test; import java.util.HashMap; @@ -358,6 +360,24 @@ public class FilterTest extends BaseTest { assertThat(filter(where("null-key").size(6)).apply(createPredicateContext(json))).isEqualTo(false); } + //---------------------------------------------------------------------------- + // + // SUBSETOF + // + //---------------------------------------------------------------------------- + @Test + public void array_subsetof_evals() { + // list is a superset + List list = Lists.newArrayList("a", "b", "c", "d", "e", "f", "g"); + assertThat(filter(where("string-arr").subsetof(list)).apply(createPredicateContext(json))).isEqualTo(true); + // list is exactly the same set (but in a different order) + list = Lists.newArrayList("e", "d", "b", "c", "a"); + assertThat(filter(where("string-arr").subsetof(list)).apply(createPredicateContext(json))).isEqualTo(true); + // list is missing one element + list = Lists.newArrayList("a", "b", "c", "d"); + assertThat(filter(where("string-arr").subsetof(list)).apply(createPredicateContext(json))).isEqualTo(false); + } + //---------------------------------------------------------------------------- // // EXISTS