Skip to content

Commit

Permalink
Merge pull request #365 from dhalperi/337-subset-with-tests
Browse files Browse the repository at this point in the history
implement subsetof filter operator
  • Loading branch information
kallestenflo committed Jun 30, 2017
2 parents 2cfcf3c + 7a6fa59 commit 65ceb10
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
27 changes: 27 additions & 0 deletions json-path/src/main/java/com/jayway/jsonpath/Criteria.java
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,33 @@ public Criteria nin(Collection<?> c) {
return this;
}

/**
* The <code>subsetof</code> 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 <code>subsetof</code> 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 <code>all</code> operator is similar to $in, but instead of matching any value
* in the specified array all values in the array must be matched.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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){
Expand Down Expand Up @@ -264,4 +265,34 @@ private String getInput(ValueNode valueNode) {
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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public enum RelationalOperator {
EXISTS("EXISTS"),
TYPE("TYPE"),
MATCHES("MATCHES"),
EMPTY("EMPTY");
EMPTY("EMPTY"),
SUBSETOF("SUBSETOF");

private final String operatorString;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,15 @@ public boolean contains(ValueNode node){
return nodes.contains(node);
}

public boolean subsetof(ValueListNode right) {
for (ValueNode leftNode : nodes) {
if (!right.nodes.contains(leftNode)) {
return false;
}
}
return true;
}

public List<ValueNode> getNodes() {
return Collections.unmodifiableList(nodes);
}
Expand Down
10 changes: 10 additions & 0 deletions json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jayway.jsonpath;

import java.util.Collections;
import org.assertj.core.api.Assertions;
import org.junit.Test;

Expand Down Expand Up @@ -142,6 +143,15 @@ public void a_size_filter_can_be_serialized() {
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() {

Expand Down
20 changes: 20 additions & 0 deletions json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -358,6 +360,24 @@ public void null_size_evals() {
assertThat(filter(where("null-key").size(6)).apply(createPredicateContext(json))).isEqualTo(false);
}

//----------------------------------------------------------------------------
//
// SUBSETOF
//
//----------------------------------------------------------------------------
@Test
public void array_subsetof_evals() {
// list is a superset
List<String> 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
Expand Down

0 comments on commit 65ceb10

Please sign in to comment.