Browse Source

Merge pull request #538 from MarcP04/master

Add support of new operators ANYOF and NONEOF
pull/543/head
kallestenflo 6 years ago committed by GitHub
parent
commit
5a09489c32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 44
      README.md
  2. 54
      json-path/src/main/java/com/jayway/jsonpath/Criteria.java
  3. 79
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java
  4. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java
  5. 20
      json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java
  6. 31
      json-path/src/test/java/com/jayway/jsonpath/FilterTest.java

44
README.md

@ -77,13 +77,13 @@ Functions
Functions can be invoked at the tail end of a path - the input to a function is the output of the path expression. Functions can be invoked at the tail end of a path - the input to a function is the output of the path expression.
The function output is dictated by the function itself. The function output is dictated by the function itself.
| Function | Description | Output | | Function | Description | Output |
| :------------------------ | :----------------------------------------------------------------- |-----------| | :------------------------ | :------------------------------------------------------------------ |-----------|
| min() | Provides the min value of an array of numbers | Double | | min() | Provides the min value of an array of numbers | Double |
| max() | Provides the max value of an array of numbers | Double | | max() | Provides the max value of an array of numbers | Double |
| avg() | Provides the average value of an array of numbers | Double | | avg() | Provides the average value of an array of numbers | Double |
| stddev() | Provides the standard deviation value of an array of numbers | Double | | stddev() | Provides the standard deviation value of an array of numbers | Double |
| length() | Provides the length of an array | Integer | | length() | Provides the length of an array | Integer |
Filter Operators Filter Operators
@ -91,20 +91,22 @@ Filter Operators
Filters are logical expressions used to filter arrays. A typical filter would be `[?(@.age > 18)]` where `@` represents the current item being processed. More complex filters can be created with logical operators `&&` and `||`. String literals must be enclosed by single or double quotes (`[?(@.color == 'blue')]` or `[?(@.color == "blue")]`). Filters are logical expressions used to filter arrays. A typical filter would be `[?(@.age > 18)]` where `@` represents the current item being processed. More complex filters can be created with logical operators `&&` and `||`. String literals must be enclosed by single or double quotes (`[?(@.color == 'blue')]` or `[?(@.color == "blue")]`).
| Operator | Description | | Operator | Description |
| :----------------------- | :---------------------------------------------------------------- | | :----------------------- | :-------------------------------------------------------------------- |
| == | left is equal to right (note that 1 is not equal to '1') | | == | left is equal to right (note that 1 is not equal to '1') |
| != | left is not equal to right | | != | left is not equal to right |
| < | left is less than right | | < | left is less than right |
| <= | left is less or equal to right | | <= | left is less or equal to right |
| > | left is greater than right | | > | left is greater than right |
| >= | left is greater than or equal to right | | >= | left is greater than or equal to right |
| =~ | left matches regular expression [?(@.name =~ /foo.*?/i)] | | =~ | left matches regular expression [?(@.name =~ /foo.*?/i)] |
| in | left exists in right [?(@.size in ['S', 'M'])] | | in | left exists in right [?(@.size in ['S', 'M'])] |
| nin | left does not exists in right | | nin | left does not exists in right |
| subsetof | left is a subset of right [?(@.sizes subsetof ['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 | | anyof | left has an intersection with right [?(@.sizes anyof ['M', 'L'])] |
| empty | left (array or string) should be empty | | noneof | left has no intersection with right [?(@.sizes noneof ['M', 'L'])] |
| size | size of left (array or string) should match right |
| empty | left (array or string) should be empty |
Path Examples Path Examples

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

@ -19,6 +19,7 @@ import com.jayway.jsonpath.internal.Utils;
import com.jayway.jsonpath.internal.filter.RelationalExpressionNode; import com.jayway.jsonpath.internal.filter.RelationalExpressionNode;
import com.jayway.jsonpath.internal.filter.RelationalOperator; import com.jayway.jsonpath.internal.filter.RelationalOperator;
import com.jayway.jsonpath.internal.filter.ValueNode; import com.jayway.jsonpath.internal.filter.ValueNode;
import com.jayway.jsonpath.internal.filter.ValueNodes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -28,9 +29,8 @@ import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static com.jayway.jsonpath.internal.Utils.notNull; import static com.jayway.jsonpath.internal.Utils.notNull;
import com.jayway.jsonpath.internal.filter.ValueNodes;
import static com.jayway.jsonpath.internal.filter.ValueNodes.ValueListNode;
import static com.jayway.jsonpath.internal.filter.ValueNodes.PredicateNode; import static com.jayway.jsonpath.internal.filter.ValueNodes.PredicateNode;
import static com.jayway.jsonpath.internal.filter.ValueNodes.ValueListNode;
/** /**
* *
@ -296,6 +296,56 @@ public class Criteria implements Predicate {
return this; return this;
} }
/**
* The <code>anyof</code> operator selects objects for which the specified field is
* an array that contain at least an element in the specified array.
*
* @param o the values to match against
* @return the criteria
*/
public Criteria anyof(Object... o) {
return subsetof(Arrays.asList(o));
}
/**
* The <code>anyof</code> operator selects objects for which the specified field is
* an array that contain at least an element in the specified array.
*
* @param c the values to match against
* @return the criteria
*/
public Criteria anyof(Collection<?> c) {
notNull(c, "collection can not be null");
this.criteriaType = RelationalOperator.ANYOF;
this.right = new ValueListNode(c);
return this;
}
/**
* The <code>noneof</code> operator selects objects for which the specified field is
* an array that does not contain any of the elements of the specified array.
*
* @param o the values to match against
* @return the criteria
*/
public Criteria noneof(Object... o) {
return subsetof(Arrays.asList(o));
}
/**
* The <code>noneof</code> operator selects objects for which the specified field is
* an array that does not contain any of the elements of the specified array.
*
* @param c the values to match against
* @return the criteria
*/
public Criteria noneof(Collection<?> c) {
notNull(c, "collection can not be null");
this.criteriaType = RelationalOperator.NONEOF;
this.right = new ValueListNode(c);
return this;
}
/** /**
* The <code>all</code> operator is similar to $in, but instead of matching any value * 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. * in the specified array all values in the array must be matched.

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

@ -2,11 +2,13 @@ package com.jayway.jsonpath.internal.filter;
import com.jayway.jsonpath.JsonPathException; import com.jayway.jsonpath.JsonPathException;
import com.jayway.jsonpath.Predicate; import com.jayway.jsonpath.Predicate;
import static com.jayway.jsonpath.internal.filter.ValueNodes.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static com.jayway.jsonpath.internal.filter.ValueNodes.PatternNode;
import static com.jayway.jsonpath.internal.filter.ValueNodes.ValueListNode;
public class EvaluatorFactory { public class EvaluatorFactory {
private static final Map<RelationalOperator, Evaluator> evaluators = new HashMap<RelationalOperator, Evaluator>(); private static final Map<RelationalOperator, Evaluator> evaluators = new HashMap<RelationalOperator, Evaluator>();
@ -31,6 +33,8 @@ public class EvaluatorFactory {
evaluators.put(RelationalOperator.MATCHES, new PredicateMatchEvaluator()); evaluators.put(RelationalOperator.MATCHES, new PredicateMatchEvaluator());
evaluators.put(RelationalOperator.TYPE, new TypeEvaluator()); evaluators.put(RelationalOperator.TYPE, new TypeEvaluator());
evaluators.put(RelationalOperator.SUBSETOF, new SubsetOfEvaluator()); evaluators.put(RelationalOperator.SUBSETOF, new SubsetOfEvaluator());
evaluators.put(RelationalOperator.ANYOF, new AnyOfEvaluator());
evaluators.put(RelationalOperator.NONEOF, new NoneOfEvaluator());
} }
public static Evaluator createEvaluator(RelationalOperator operator){ public static Evaluator createEvaluator(RelationalOperator operator){
@ -296,4 +300,77 @@ public class EvaluatorFactory {
} }
} }
private static class AnyOfEvaluator implements Evaluator {
@Override
public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
ValueListNode rightValueListNode;
if (right.isJsonNode()) {
ValueNode vn = right.asJsonNode().asValueListNode(ctx);
if (vn.isUndefinedNode()) {
return false;
} else {
rightValueListNode = vn.asValueListNode();
}
} else {
rightValueListNode = right.asValueListNode();
}
ValueListNode leftValueListNode;
if (left.isJsonNode()) {
ValueNode vn = left.asJsonNode().asValueListNode(ctx);
if (vn.isUndefinedNode()) {
return false;
} else {
leftValueListNode = vn.asValueListNode();
}
} else {
leftValueListNode = left.asValueListNode();
}
for (ValueNode leftValueNode : leftValueListNode) {
for (ValueNode rightValueNode : rightValueListNode) {
if (leftValueNode.equals(rightValueNode)) {
return true;
}
}
}
return false;
}
}
private static class NoneOfEvaluator implements Evaluator {
@Override
public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
ValueListNode rightValueListNode;
if (right.isJsonNode()) {
ValueNode vn = right.asJsonNode().asValueListNode(ctx);
if (vn.isUndefinedNode()) {
return false;
} else {
rightValueListNode = vn.asValueListNode();
}
} else {
rightValueListNode = right.asValueListNode();
}
ValueListNode leftValueListNode;
if (left.isJsonNode()) {
ValueNode vn = left.asJsonNode().asValueListNode(ctx);
if (vn.isUndefinedNode()) {
return false;
} else {
leftValueListNode = vn.asValueListNode();
}
} else {
leftValueListNode = left.asValueListNode();
}
for (ValueNode leftValueNode : leftValueListNode) {
for (ValueNode rightValueNode : rightValueListNode) {
if (leftValueNode.equals(rightValueNode)) {
return false;
}
}
}
return true;
}
}
} }

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

@ -30,7 +30,9 @@ public enum RelationalOperator {
TYPE("TYPE"), TYPE("TYPE"),
MATCHES("MATCHES"), MATCHES("MATCHES"),
EMPTY("EMPTY"), EMPTY("EMPTY"),
SUBSETOF("SUBSETOF"); SUBSETOF("SUBSETOF"),
ANYOF("ANYOF"),
NONEOF("NONEOF");
private final String operatorString; private final String operatorString;

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

@ -1,9 +1,9 @@
package com.jayway.jsonpath; package com.jayway.jsonpath;
import java.util.Collections;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.Test; import org.junit.Test;
import java.util.Collections;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static com.jayway.jsonpath.Criteria.where; import static com.jayway.jsonpath.Criteria.where;
@ -152,6 +152,24 @@ public class FilterParseTest {
assertThat(filter).isEqualTo(parsed); assertThat(filter).isEqualTo(parsed);
} }
@Test
public void a_anyof_filter_can_be_serialized() {
String filter = filter(where("a").anyof(Collections.emptyList())).toString();
String parsed = parse("[?(@['a'] ANYOF [])]").toString();
assertThat(filter).isEqualTo(parsed);
}
@Test
public void a_noneof_filter_can_be_serialized() {
String filter = filter(where("a").noneof(Collections.emptyList())).toString();
String parsed = parse("[?(@['a'] NONEOF [])]").toString();
assertThat(filter).isEqualTo(parsed);
}
@Test @Test
public void a_exists_filter_can_be_serialized() { public void a_exists_filter_can_be_serialized() {

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

@ -1,6 +1,5 @@
package com.jayway.jsonpath; package com.jayway.jsonpath;
import java.util.ArrayList;
import org.assertj.core.util.Lists; import org.assertj.core.util.Lists;
import org.junit.Test; import org.junit.Test;
@ -379,6 +378,36 @@ public class FilterTest extends BaseTest {
assertThat(filter(where("string-arr").subsetof(list)).apply(createPredicateContext(json))).isEqualTo(false); assertThat(filter(where("string-arr").subsetof(list)).apply(createPredicateContext(json))).isEqualTo(false);
} }
//----------------------------------------------------------------------------
//
// ANYOF
//
//----------------------------------------------------------------------------
@Test
public void array_anyof_evals() {
List<String> list = Lists.newArrayList("a", "z");
assertThat(filter(where("string-arr").anyof(list)).apply(createPredicateContext(json))).isEqualTo(true);
list = Lists.newArrayList("z", "b", "a");
assertThat(filter(where("string-arr").anyof(list)).apply(createPredicateContext(json))).isEqualTo(true);
list = Lists.newArrayList("x", "y", "z");
assertThat(filter(where("string-arr").anyof(list)).apply(createPredicateContext(json))).isEqualTo(false);
}
//----------------------------------------------------------------------------
//
// NONEOF
//
//----------------------------------------------------------------------------
@Test
public void array_noneof_evals() {
List<String> list = Lists.newArrayList("a", "z");
assertThat(filter(where("string-arr").noneof(list)).apply(createPredicateContext(json))).isEqualTo(false);
list = Lists.newArrayList("z", "b", "a");
assertThat(filter(where("string-arr").noneof(list)).apply(createPredicateContext(json))).isEqualTo(false);
list = Lists.newArrayList("x", "y", "z");
assertThat(filter(where("string-arr").noneof(list)).apply(createPredicateContext(json))).isEqualTo(true);
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// //
// EXISTS // EXISTS

Loading…
Cancel
Save