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. 188
      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

188
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 static final Object NOT_SET = new Object();
private final String key; private final JsonPath key;
private final List<Criteria> criteriaChain; private final List<Criteria> criteriaChain;
@ -61,17 +61,17 @@ public class Criteria {
notEmpty(key, "key can not be null or empty"); notEmpty(key, "key can not be null or empty");
this.criteriaChain = new ArrayList<Criteria>(); this.criteriaChain = new ArrayList<Criteria>();
this.criteriaChain.add(this); this.criteriaChain.add(this);
this.key = key; this.key = JsonPath.compile(key);
} }
private Criteria(List<Criteria> criteriaChain, String key) { private Criteria(List<Criteria> criteriaChain, String key) {
notEmpty(key, "key can not be null or empty"); notEmpty(key, "key can not be null or empty");
this.criteriaChain = criteriaChain; this.criteriaChain = criteriaChain;
this.criteriaChain.add(this); this.criteriaChain.add(this);
this.key = key; this.key = JsonPath.compile(key);
} }
public String getKey() { public JsonPath getKey() {
return this.key; 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) { boolean singleObjectApply(Map<String, Object> map) {
for (CriteriaType key : this.criteria.keySet()) { for (CriteriaType key : this.criteria.keySet()) {
Object actualVal = map.get(this.key); Object actualVal = readSafely(this.key, map);
Object expectedVal = this.criteria.get(key); final Object expectedVal = this.criteria.get(key);
if (CriteriaType.GT.equals(key)) { if (CriteriaType.GT.equals(key)) {
@ -111,10 +130,14 @@ public class Criteria {
return false; return false;
} }
Number expectedNumber = (Number) expectedVal; final Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal; 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)) { } else if (CriteriaType.GTE.equals(key)) {
@ -122,10 +145,14 @@ public class Criteria {
return false; return false;
} }
Number expectedNumber = (Number) expectedVal; final Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal; 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)) { } else if (CriteriaType.LT.equals(key)) {
@ -133,10 +160,14 @@ public class Criteria {
return false; return false;
} }
Number expectedNumber = (Number) expectedVal; final Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal; 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)) { } else if (CriteriaType.LTE.equals(key)) {
@ -144,20 +175,31 @@ public class Criteria {
return false; return false;
} }
Number expectedNumber = (Number) expectedVal; final Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal; 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)) { } else if (CriteriaType.NE.equals(key)) {
if (expectedVal == null && actualVal == null) {
return false; return objectOrAnyCollectionItemMatches(actualVal, new Predicate<Object>() {
}
if (expectedVal == null) { @Override
return true; public boolean accept(Object value) {
} else { if (expectedVal == null && value == null) {
return !expectedVal.equals(actualVal); return false;
} }
if (expectedVal == null) {
return true;
} else {
return !expectedVal.equals(value);
}
}
});
} else if (CriteriaType.IN.equals(key)) { } else if (CriteriaType.IN.equals(key)) {
@ -186,36 +228,53 @@ public class Criteria {
} else if (CriteriaType.EXISTS.equals(key)) { } else if (CriteriaType.EXISTS.equals(key)) {
boolean exp = (Boolean) expectedVal; final boolean exp = (Boolean) expectedVal;
boolean act = map.containsKey(this.key); 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;
}
return act == exp; });
} else if (CriteriaType.TYPE.equals(key)) { } else if (CriteriaType.TYPE.equals(key)) {
Class<?> exp = (Class<?>) expectedVal; final Class<?> exp = (Class<?>) expectedVal;
Class<?> act = null; return objectOrAnyCollectionItemMatches(actualVal, new Predicate<Object>() {
if (map.containsKey(this.key)) {
Object actVal = map.get(this.key); @Override
if (actVal != null) { public boolean accept(Object value) {
act = actVal.getClass(); Class<?> act = value == null ? null : value.getClass();
if (act == null) {
return false;
} else {
return act.equals(exp);
} }
} }
if (act == null) { });
return false;
} else {
return act.equals(exp);
}
} else if (CriteriaType.REGEX.equals(key)) { } else if (CriteriaType.REGEX.equals(key)) {
final Pattern exp = (Pattern) expectedVal;
return objectOrAnyCollectionItemMatches(actualVal, new Predicate<String>() {
@Override
Pattern exp = (Pattern) expectedVal; public boolean accept(String value) {
String act = (String) actualVal; return value != null && exp.matcher(value).matches();
if (act == null) { }
return false; });
}
return exp.matcher(act).matches();
} else { } else {
throw new UnsupportedOperationException("Criteria type not supported: " + key.name()); throw new UnsupportedOperationException("Criteria type not supported: " + key.name());
@ -234,11 +293,18 @@ public class Criteria {
} }
return true; return true;
} else { } else {
if (isValue == null) { Object actualVal = readSafely(this.key, map);
return (map.get(key) == null); return objectOrAnyCollectionItemMatches(actualVal, new Predicate<Object>() {
} else { @Override
return isValue.equals(map.get(key)); public boolean accept(Object value) {
} if (isValue == null) {
return value == null;
} else {
return isValue.equals(value);
}
}
});
} }
} else { } else {
@ -375,6 +441,7 @@ public class Criteria {
*/ */
public Criteria in(Collection<?> c) { public Criteria in(Collection<?> c) {
notNull(c, "collection can not be null"); notNull(c, "collection can not be null");
checkFilterCanBeApplied(CriteriaType.IN);
criteria.put(CriteriaType.IN, c); criteria.put(CriteriaType.IN, c);
return this; return this;
} }
@ -399,6 +466,7 @@ public class Criteria {
*/ */
public Criteria nin(Collection<?> c) { public Criteria nin(Collection<?> c) {
notNull(c, "collection can not be null"); notNull(c, "collection can not be null");
checkFilterCanBeApplied(CriteriaType.NIN);
criteria.put(CriteriaType.NIN, c); criteria.put(CriteriaType.NIN, c);
return this; return this;
} }
@ -421,6 +489,7 @@ public class Criteria {
*/ */
public Criteria all(Collection<?> c) { public Criteria all(Collection<?> c) {
notNull(c, "collection can not be null"); notNull(c, "collection can not be null");
checkFilterCanBeApplied(CriteriaType.ALL);
criteria.put(CriteriaType.ALL, c); criteria.put(CriteriaType.ALL, c);
return this; return this;
} }
@ -432,6 +501,7 @@ public class Criteria {
* @return * @return
*/ */
public Criteria size(int s) { public Criteria size(int s) {
checkFilterCanBeApplied(CriteriaType.SIZE);
criteria.put(CriteriaType.SIZE, s); criteria.put(CriteriaType.SIZE, s);
return this; return this;
} }
@ -505,6 +575,16 @@ public class Criteria {
criteriaChain.add(new Criteria("$and").is(asList(criteria))); criteriaChain.add(new Criteria("$and").is(asList(criteria)));
return this; 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) { public MapFilter addCriteria(Criteria criteria) {
Criteria existing = this.criteria.get(criteria.getKey()); Criteria existing = this.criteria.get(criteria.getKey().getPath());
String key = criteria.getKey(); String key = criteria.getKey().getPath();
if (existing == null) { if (existing == null) {
this.criteria.put(key, criteria); this.criteria.put(key, criteria);
} else { } else {

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

@ -1,5 +1,7 @@
package com.jayway.jsonpath; package com.jayway.jsonpath;
import net.minidev.json.parser.JSONParser;
import org.junit.Test; import org.junit.Test;
import java.util.Collections; import java.util.Collections;
@ -382,5 +384,41 @@ public class FilterTest {
assertEquals(1, res.get(0).intValue()); 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