diff --git a/README.md b/README.md
index 2b6be0e9..b7826eef 100644
--- a/README.md
+++ b/README.md
@@ -100,6 +100,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 |
+| 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 30e1eda1..92f9b2bc 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 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 subsetof(Object... o) {
+ return subsetof(Arrays.asList(o));
+ }
+
+ /**
+ * 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 subsetof(Collection> c) {
+ notNull(c, "collection can not be null");
+ this.criteriaType = RelationalOperator.SUBSETOF;
+ 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..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,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.SUBSETOF, new SubsetOfEvaluator());
}
public static Evaluator createEvaluator(RelationalOperator operator){
@@ -264,4 +265,34 @@ public class EvaluatorFactory {
return input;
}
}
+
+ private static class SubsetOfEvaluator 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.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 4575f60b..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
@@ -29,7 +29,8 @@ public enum RelationalOperator {
EXISTS("EXISTS"),
TYPE("TYPE"),
MATCHES("MATCHES"),
- EMPTY("EMPTY");
+ EMPTY("EMPTY"),
+ 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 9989dd95..5135f848 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
@@ -713,6 +713,15 @@ public abstract class ValueNode {
return nodes.contains(node);
}
+ public boolean subsetof(ValueListNode right) {
+ for (ValueNode leftNode : nodes) {
+ if (!right.nodes.contains(leftNode)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public List getNodes() {
return Collections.unmodifiableList(nodes);
}
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