Skip to content

Commit 65ceb10

Browse files
authored
Merge pull request #365 from dhalperi/337-subset-with-tests
implement subsetof filter operator
2 parents 2cfcf3c + 7a6fa59 commit 65ceb10

File tree

7 files changed

+100
-1
lines changed

7 files changed

+100
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ Filters are logical expressions used to filter arrays. A typical filter would be
100100
| =~ | left matches regular expression [?(@.name =~ /foo.*?/i)] |
101101
| in | left exists in right [?(@.size in ['S', 'M'])] |
102102
| nin | left does not exists in right |
103+
| subsetof | left is a subset of right [?(@.sizes subsetof ['S', 'M', 'L'])] |
103104
| size | size of left (array or string) should match right |
104105
| empty | left (array or string) should be empty |
105106

json-path/src/main/java/com/jayway/jsonpath/Criteria.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,33 @@ public Criteria nin(Collection<?> c) {
267267
return this;
268268
}
269269

270+
/**
271+
* The <code>subsetof</code> operator selects objects for which the specified field is
272+
* an array whose elements comprise a subset of the set comprised by the elements of
273+
* the specified array.
274+
*
275+
* @param o the values to match against
276+
* @return the criteria
277+
*/
278+
public Criteria subsetof(Object... o) {
279+
return subsetof(Arrays.asList(o));
280+
}
281+
282+
/**
283+
* The <code>subsetof</code> operator selects objects for which the specified field is
284+
* an array whose elements comprise a subset of the set comprised by the elements of
285+
* the specified array.
286+
*
287+
* @param c the values to match against
288+
* @return the criteria
289+
*/
290+
public Criteria subsetof(Collection<?> c) {
291+
notNull(c, "collection can not be null");
292+
this.criteriaType = RelationalOperator.SUBSETOF;
293+
this.right = new ValueNode.ValueListNode(c);
294+
return this;
295+
}
296+
270297
/**
271298
* The <code>all</code> operator is similar to $in, but instead of matching any value
272299
* in the specified array all values in the array must be matched.

json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class EvaluatorFactory {
2929
evaluators.put(RelationalOperator.CONTAINS, new ContainsEvaluator());
3030
evaluators.put(RelationalOperator.MATCHES, new PredicateMatchEvaluator());
3131
evaluators.put(RelationalOperator.TYPE, new TypeEvaluator());
32+
evaluators.put(RelationalOperator.SUBSETOF, new SubsetOfEvaluator());
3233
}
3334

3435
public static Evaluator createEvaluator(RelationalOperator operator){
@@ -264,4 +265,34 @@ private String getInput(ValueNode valueNode) {
264265
return input;
265266
}
266267
}
268+
269+
private static class SubsetOfEvaluator implements Evaluator {
270+
@Override
271+
public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
272+
ValueNode.ValueListNode rightValueListNode;
273+
if(right.isJsonNode()){
274+
ValueNode vn = right.asJsonNode().asValueListNode(ctx);
275+
if(vn.isUndefinedNode()){
276+
return false;
277+
} else {
278+
rightValueListNode = vn.asValueListNode();
279+
}
280+
} else {
281+
rightValueListNode = right.asValueListNode();
282+
}
283+
ValueNode.ValueListNode leftValueListNode;
284+
if(left.isJsonNode()){
285+
ValueNode vn = left.asJsonNode().asValueListNode(ctx);
286+
if(vn.isUndefinedNode()){
287+
return false;
288+
} else {
289+
leftValueListNode = vn.asValueListNode();
290+
}
291+
} else {
292+
leftValueListNode = left.asValueListNode();
293+
}
294+
return leftValueListNode.subsetof(rightValueListNode);
295+
}
296+
}
297+
267298
}

json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public enum RelationalOperator {
2929
EXISTS("EXISTS"),
3030
TYPE("TYPE"),
3131
MATCHES("MATCHES"),
32-
EMPTY("EMPTY");
32+
EMPTY("EMPTY"),
33+
SUBSETOF("SUBSETOF");
3334

3435
private final String operatorString;
3536

json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,15 @@ public boolean contains(ValueNode node){
713713
return nodes.contains(node);
714714
}
715715

716+
public boolean subsetof(ValueListNode right) {
717+
for (ValueNode leftNode : nodes) {
718+
if (!right.nodes.contains(leftNode)) {
719+
return false;
720+
}
721+
}
722+
return true;
723+
}
724+
716725
public List<ValueNode> getNodes() {
717726
return Collections.unmodifiableList(nodes);
718727
}

json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.jayway.jsonpath;
22

3+
import java.util.Collections;
34
import org.assertj.core.api.Assertions;
45
import org.junit.Test;
56

@@ -142,6 +143,15 @@ public void a_size_filter_can_be_serialized() {
142143
assertThat(filter).isEqualTo(parsed);
143144
}
144145

146+
@Test
147+
public void a_subsetof_filter_can_be_serialized() {
148+
149+
String filter = filter(where("a").subsetof(Collections.emptyList())).toString();
150+
String parsed = parse("[?(@['a'] SUBSETOF [])]").toString();
151+
152+
assertThat(filter).isEqualTo(parsed);
153+
}
154+
145155
@Test
146156
public void a_exists_filter_can_be_serialized() {
147157

json-path/src/test/java/com/jayway/jsonpath/FilterTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.jayway.jsonpath;
22

3+
import java.util.ArrayList;
4+
import org.assertj.core.util.Lists;
35
import org.junit.Test;
46

57
import java.util.HashMap;
@@ -358,6 +360,24 @@ public void null_size_evals() {
358360
assertThat(filter(where("null-key").size(6)).apply(createPredicateContext(json))).isEqualTo(false);
359361
}
360362

363+
//----------------------------------------------------------------------------
364+
//
365+
// SUBSETOF
366+
//
367+
//----------------------------------------------------------------------------
368+
@Test
369+
public void array_subsetof_evals() {
370+
// list is a superset
371+
List<String> list = Lists.newArrayList("a", "b", "c", "d", "e", "f", "g");
372+
assertThat(filter(where("string-arr").subsetof(list)).apply(createPredicateContext(json))).isEqualTo(true);
373+
// list is exactly the same set (but in a different order)
374+
list = Lists.newArrayList("e", "d", "b", "c", "a");
375+
assertThat(filter(where("string-arr").subsetof(list)).apply(createPredicateContext(json))).isEqualTo(true);
376+
// list is missing one element
377+
list = Lists.newArrayList("a", "b", "c", "d");
378+
assertThat(filter(where("string-arr").subsetof(list)).apply(createPredicateContext(json))).isEqualTo(false);
379+
}
380+
361381
//----------------------------------------------------------------------------
362382
//
363383
// EXISTS

0 commit comments

Comments
 (0)