Browse Source

Merge pull request #16 from jochenberger/use-jsonpath-in-filters

Use jsonpath in filters
pull/18/head
kallestenflo 12 years ago
parent
commit
5993e05b73
  1. 156
      json-path/src/main/java/com/jayway/jsonpath/Criteria.java
  2. 4
      json-path/src/main/java/com/jayway/jsonpath/Filter.java
  3. 38
      json-path/src/test/java/com/jayway/jsonpath/FilterTest.java

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

@ -48,7 +48,7 @@ public class Criteria {
*/
private static final Object NOT_SET = new Object();
private final String key;
private final JsonPath key;
private final List<Criteria> criteriaChain;
@ -61,17 +61,17 @@ public class Criteria {
notEmpty(key, "key can not be null or empty");
this.criteriaChain = new ArrayList<Criteria>();
this.criteriaChain.add(this);
this.key = key;
this.key = JsonPath.compile(key);
}
private Criteria(List<Criteria> criteriaChain, String key) {
notEmpty(key, "key can not be null or empty");
this.criteriaChain = criteriaChain;
this.criteriaChain.add(this);
this.key = key;
this.key = JsonPath.compile(key);
}
public String getKey() {
public JsonPath getKey() {
return this.key;
}
@ -95,15 +95,34 @@ public class Criteria {
}
}
private static Object readSafely(JsonPath path, Map<String, Object> map){
try{
return path.read(map);
} catch (InvalidPathException e){
return null;
}
}
private static <T> boolean objectOrAnyCollectionItemMatches(final Object singleObjectOrCollection,
final Predicate<T> predicate){
if (singleObjectOrCollection instanceof Collection) {
Iterator it = ((Collection) singleObjectOrCollection).iterator();
while (it.hasNext()) {
if (predicate.accept((T) it.next())) {
return true;
}
}
return false;
}
return predicate.accept((T) singleObjectOrCollection);
}
boolean singleObjectApply(Map<String, Object> map) {
for (CriteriaType key : this.criteria.keySet()) {
Object actualVal = map.get(this.key);
Object expectedVal = this.criteria.get(key);
Object actualVal = readSafely(this.key, map);
final Object expectedVal = this.criteria.get(key);
if (CriteriaType.GT.equals(key)) {
@ -111,10 +130,14 @@ public class Criteria {
return false;
}
Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal;
final Number expectedNumber = (Number) expectedVal;
return objectOrAnyCollectionItemMatches(actualVal, new Predicate<Number>() {
return (actualNumber.doubleValue() > expectedNumber.doubleValue());
@Override
public boolean accept(Number value) {
return (value.doubleValue() > expectedNumber.doubleValue());
}
});
} else if (CriteriaType.GTE.equals(key)) {
@ -122,10 +145,14 @@ public class Criteria {
return false;
}
Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal;
final Number expectedNumber = (Number) expectedVal;
return objectOrAnyCollectionItemMatches(actualVal, new Predicate<Number>() {
return (actualNumber.doubleValue() >= expectedNumber.doubleValue());
@Override
public boolean accept(Number value) {
return (value.doubleValue() >= expectedNumber.doubleValue());
}
});
} else if (CriteriaType.LT.equals(key)) {
@ -133,10 +160,14 @@ public class Criteria {
return false;
}
Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal;
final Number expectedNumber = (Number) expectedVal;
return objectOrAnyCollectionItemMatches(actualVal, new Predicate<Number>() {
return (actualNumber.doubleValue() < expectedNumber.doubleValue());
@Override
public boolean accept(Number value) {
return (value.doubleValue() < expectedNumber.doubleValue());
}
});
} else if (CriteriaType.LTE.equals(key)) {
@ -144,20 +175,31 @@ public class Criteria {
return false;
}
Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal;
final Number expectedNumber = (Number) expectedVal;
return objectOrAnyCollectionItemMatches(actualVal, new Predicate<Number>() {
return (actualNumber.doubleValue() <= expectedNumber.doubleValue());
@Override
public boolean accept(Number value) {
return (value.doubleValue() <= expectedNumber.doubleValue());
}
});
} else if (CriteriaType.NE.equals(key)) {
if (expectedVal == null && actualVal == null) {
return objectOrAnyCollectionItemMatches(actualVal, new Predicate<Object>() {
@Override
public boolean accept(Object value) {
if (expectedVal == null && value == null) {
return false;
}
if (expectedVal == null) {
return true;
} else {
return !expectedVal.equals(actualVal);
return !expectedVal.equals(value);
}
}
});
} else if (CriteriaType.IN.equals(key)) {
@ -186,36 +228,53 @@ public class Criteria {
} else if (CriteriaType.EXISTS.equals(key)) {
boolean exp = (Boolean) expectedVal;
boolean act = map.containsKey(this.key);
final boolean exp = (Boolean) expectedVal;
return objectOrAnyCollectionItemMatches(map, new Predicate<Object>() {
@Override
public boolean accept(final Object value) {
boolean act = true;
try {
Object val = getKey().read(value);
if(val instanceof Collection){
act = !((Collection) val).isEmpty();
}
} catch (InvalidPathException e) {
act = false;
}
return act == exp;
}
});
} else if (CriteriaType.TYPE.equals(key)) {
Class<?> exp = (Class<?>) expectedVal;
Class<?> act = null;
if (map.containsKey(this.key)) {
Object actVal = map.get(this.key);
if (actVal != null) {
act = actVal.getClass();
}
}
final Class<?> exp = (Class<?>) expectedVal;
return objectOrAnyCollectionItemMatches(actualVal, new Predicate<Object>() {
@Override
public boolean accept(Object value) {
Class<?> act = value == null ? null : value.getClass();
if (act == null) {
return false;
} else {
return act.equals(exp);
}
}
});
} else if (CriteriaType.REGEX.equals(key)) {
final Pattern exp = (Pattern) expectedVal;
return objectOrAnyCollectionItemMatches(actualVal, new Predicate<String>() {
Pattern exp = (Pattern) expectedVal;
String act = (String) actualVal;
if (act == null) {
return false;
@Override
public boolean accept(String value) {
return value != null && exp.matcher(value).matches();
}
return exp.matcher(act).matches();
});
} else {
throw new UnsupportedOperationException("Criteria type not supported: " + key.name());
@ -234,11 +293,18 @@ public class Criteria {
}
return true;
} else {
Object actualVal = readSafely(this.key, map);
return objectOrAnyCollectionItemMatches(actualVal, new Predicate<Object>() {
@Override
public boolean accept(Object value) {
if (isValue == null) {
return (map.get(key) == null);
return value == null;
} else {
return isValue.equals(map.get(key));
return isValue.equals(value);
}
}
});
}
} else {
@ -375,6 +441,7 @@ public class Criteria {
*/
public Criteria in(Collection<?> c) {
notNull(c, "collection can not be null");
checkFilterCanBeApplied(CriteriaType.IN);
criteria.put(CriteriaType.IN, c);
return this;
}
@ -399,6 +466,7 @@ public class Criteria {
*/
public Criteria nin(Collection<?> c) {
notNull(c, "collection can not be null");
checkFilterCanBeApplied(CriteriaType.NIN);
criteria.put(CriteriaType.NIN, c);
return this;
}
@ -421,6 +489,7 @@ public class Criteria {
*/
public Criteria all(Collection<?> c) {
notNull(c, "collection can not be null");
checkFilterCanBeApplied(CriteriaType.ALL);
criteria.put(CriteriaType.ALL, c);
return this;
}
@ -432,6 +501,7 @@ public class Criteria {
* @return
*/
public Criteria size(int s) {
checkFilterCanBeApplied(CriteriaType.SIZE);
criteria.put(CriteriaType.SIZE, s);
return this;
}
@ -506,5 +576,15 @@ public class Criteria {
return this;
}
private void checkFilterCanBeApplied(CriteriaType type){
if (getKey().getTokenizer().size() > 2){
throw new IllegalArgumentException("Cannot use "+type+" filter on a multi-level path expression");
}
}
private interface Predicate<T> {
boolean accept(T value);
}
}

4
json-path/src/main/java/com/jayway/jsonpath/Filter.java

@ -101,8 +101,8 @@ public abstract class Filter<T> {
}
public MapFilter addCriteria(Criteria criteria) {
Criteria existing = this.criteria.get(criteria.getKey());
String key = criteria.getKey();
Criteria existing = this.criteria.get(criteria.getKey().getPath());
String key = criteria.getKey().getPath();
if (existing == null) {
this.criteria.put(key, criteria);
} else {

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

@ -1,5 +1,7 @@
package com.jayway.jsonpath;
import net.minidev.json.parser.JSONParser;
import org.junit.Test;
import java.util.Collections;
@ -383,4 +385,40 @@ public class FilterTest {
assertEquals(1, res.get(0).intValue());
}
@Test
public void filters_can_contain_json_path_expressions() throws Exception {
Object doc = JsonModel.model(DOCUMENT).getJsonObject();
assertTrue(filter(where("$.store..price").gt(10)).accept(doc));
assertFalse(filter(where("$.store..price").gte(100)).accept(doc));
assertTrue(filter(where("$.store..category").ne("fiction")).accept(doc));
assertFalse(filter(where("$.store.bicycle.color").ne("red")).accept(doc));
assertTrue(filter(where("$.store.bicycle.color").ne("blue")).accept(doc));
assertTrue(filter(where("$.store..color").exists(true)).accept(doc));
assertFalse(filter(where("$.store..flavor").exists(true)).accept(doc));
assertTrue(filter(where("$.store..color").regex(Pattern.compile("^r.d$"))).accept(doc));
assertTrue(filter(where("$.store..color").type(String.class)).accept(doc));
assertTrue(filter(where("$.store..price").is(12.99)).accept(doc));
assertFalse(filter(where("$.store..price").is(13.99)).accept(doc));
}
@Test
public void collection_based_filters_cannot_be_applied_to_multi_level_expressions(){
try{
where("$.store.*").size(4);
fail("This should have thrown an exception");
} catch(IllegalArgumentException e){
}
try{
where("$.store.*").in("foo");
fail("This should have thrown an exception");
} catch(IllegalArgumentException e){
}
}
}

Loading…
Cancel
Save