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); }