
com.jayway.jsonpath.Criteria Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of json-path Show documentation
Show all versions of json-path Show documentation
Java port of Stefan Goessner JsonPath.
/*
* Copyright 2011 the original author or authors.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.PathCompiler;
import com.jayway.jsonpath.internal.token.PredicateContextImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import static com.jayway.jsonpath.internal.Utils.join;
import static com.jayway.jsonpath.internal.Utils.notNull;
/**
*
*/
@SuppressWarnings("unchecked")
public class Criteria implements Predicate {
private static final Logger logger = LoggerFactory.getLogger(Criteria.class);
private final Path path;
private CriteriaType criteriaType;
private Object expected;
private final List criteriaChain;
private static enum CriteriaType {
EQ {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = (0 == safeCompare(expected, actual));
logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
return res;
}
},
NE {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = (0 != safeCompare(expected, actual));
logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
return res;
}
},
GT {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
if ((expected == null) ^ (actual == null)) {
return false;
}
boolean res = (0 > safeCompare(expected, actual));
logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
return res;
}
},
GTE {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
if ((expected == null) ^ (actual == null)) {
return false;
}
boolean res = (0 >= safeCompare(expected, actual));
logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
return res;
}
},
LT {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
if ((expected == null) ^ (actual == null)) {
return false;
}
boolean res = (0 < safeCompare(expected, actual));
logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
return res;
}
},
LTE {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
if ((expected == null) ^ (actual == null)) {
return false;
}
boolean res = (0 <= safeCompare(expected, actual));
logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
return res;
}
},
IN {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = false;
Collection exps = (Collection) expected;
for (Object exp : exps) {
if (0 == safeCompare(exp, actual)) {
res = true;
break;
}
}
logger.debug("[{}] {} [{}] => {}", actual, name(), join(", ", exps), res);
return res;
}
},
NIN {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
Collection nexps = (Collection) expected;
boolean res = !nexps.contains(actual);
logger.debug("[{}] {} [{}] => {}", actual, name(), join(", ", nexps), res);
return res;
}
},
ALL {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = true;
Collection exps = (Collection) expected;
if (ctx.configuration().jsonProvider().isArray(actual)) {
for (Object exp : exps) {
boolean found = false;
for (Object check : ctx.configuration().jsonProvider().toIterable(actual)) {
if (0 == safeCompare(exp, check)) {
found = true;
break;
}
}
if (!found) {
res = false;
break;
}
}
logger.debug("[{}] {} [{}] => {}", join(", ", ctx.configuration().jsonProvider().toIterable(actual)), name(), join(", ", exps), res);
} else {
res = false;
logger.debug("[{}] {} [{}] => {}", "", name(), join(", ", exps), res);
}
return res;
}
},
SIZE {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
int size = (Integer) expected;
boolean res;
if (ctx.configuration().jsonProvider().isArray(actual)) {
int length = ctx.configuration().jsonProvider().length(actual);
res = (length == size);
logger.debug("Array with size {} {} {} => {}", length, name(), size, res);
} else if (actual instanceof String) {
int length = ((String) actual).length();
res = length == size;
logger.debug("String with length {} {} {} => {}", length, name(), size, res);
} else {
res = false;
logger.debug("{} {} {} => {}", actual == null ? "null" : actual.getClass().getName(), name(), size, res);
}
return res;
}
},
EXISTS {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
//This must be handled outside
throw new UnsupportedOperationException();
}
},
TYPE {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
final Class> expType = (Class>) expected;
final Class> actType = actual == null ? null : actual.getClass();
return actType != null && expType.isAssignableFrom(actType);
}
},
REGEX {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = false;
final Pattern pattern = (Pattern) expected;
if (actual != null && actual instanceof String) {
res = pattern.matcher(actual.toString()).matches();
}
logger.debug("[{}] {} [{}] => {}", actual, name(), expected.toString(), res);
return res;
}
},
MATCHES {
@Override
boolean eval(Object expected, final Object actual, final PredicateContext ctx) {
Predicate exp = (Predicate) expected;
return exp.apply(new PredicateContextImpl(actual, ctx.root(), ctx.configuration()));
}
},
NOT_EMPTY {
@Override
boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = false;
if (actual != null) {
if (ctx.configuration().jsonProvider().isArray(actual)) {
int len = ctx.configuration().jsonProvider().length(actual);
res = (0 != len);
logger.debug("array length = {} {} => {}", len, name(), res);
} else if (actual instanceof String) {
int len = ((String) actual).length();
res = (0 != len);
logger.debug("string length = {} {} => {}", len, name(), res);
}
}
return res;
}
};
abstract boolean eval(Object expected, Object actual, PredicateContext ctx);
public static CriteriaType parse(String str) {
if ("==".equals(str)) {
return EQ;
} else if (">".equals(str)) {
return GT;
} else if (">=".equals(str)) {
return GTE;
} else if ("<".equals(str)) {
return LT;
} else if ("<=".equals(str)) {
return LTE;
} else if ("!=".equals(str)) {
return NE;
} else {
throw new UnsupportedOperationException("CriteriaType " + str + " can not be parsed");
}
}
}
private Criteria(List criteriaChain, Path path) {
if (!path.isDefinite()) {
throw new InvalidCriteriaException("A criteria path must be definite. The path " + path.toString() + " is not!");
}
this.path = path;
this.criteriaChain = criteriaChain;
this.criteriaChain.add(this);
}
private Criteria(Path path) {
this(new LinkedList(), path);
}
private Criteria(Path path, CriteriaType criteriaType, Object expected) {
this(new LinkedList(), path);
this.criteriaType = criteriaType;
this.expected = expected;
}
@Override
public boolean apply(PredicateContext ctx) {
for (Criteria criteria : criteriaChain) {
if (!criteria.eval(ctx)) {
return false;
}
}
return true;
}
private boolean eval(PredicateContext ctx) {
if (CriteriaType.EXISTS == criteriaType) {
boolean exists = ((Boolean) expected);
try {
Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options().build();
path.evaluate(ctx.item(), ctx.root(), c).getValue();
return exists;
} catch (PathNotFoundException e) {
return !exists;
}
} else {
try {
final Object actual = path.evaluate(ctx.item(), ctx.root(), ctx.configuration()).getValue();
Object expectedVal = expected;
if(expected instanceof Path){
Path expectedPath = (Path) expected;
Object doc = expectedPath.isRootPath()?ctx.root():ctx.item();
expectedVal = expectedPath.evaluate(doc, ctx.root(), ctx.configuration()).getValue();
}
return criteriaType.eval(expectedVal, actual, ctx);
} catch (ValueCompareException e) {
return false;
} catch (PathNotFoundException e) {
return false;
}
}
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key filed name
* @return the new criteria
*/
public static Criteria where(Path key) {
return new Criteria(key);
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key filed name
* @return the new criteria
*/
public static Criteria where(String key) {
return where(PathCompiler.compile(key));
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key ads new filed to criteria
* @return the criteria builder
*/
public Criteria and(String key) {
return new Criteria(this.criteriaChain, PathCompiler.compile(key));
}
/**
* Creates a criterion using equality
*
* @param o
* @return the criteria
*/
public Criteria is(Object o) {
this.criteriaType = CriteriaType.EQ;
this.expected = o;
return this;
}
/**
* Creates a criterion using equality
*
* @param o
* @return the criteria
*/
public Criteria eq(Object o) {
return is(o);
}
/**
* Creates a criterion using the != operator
*
* @param o
* @return the criteria
*/
public Criteria ne(Object o) {
this.criteriaType = CriteriaType.NE;
this.expected = o;
return this;
}
/**
* Creates a criterion using the < operator
*
* @param o
* @return the criteria
*/
public Criteria lt(Object o) {
this.criteriaType = CriteriaType.LT;
this.expected = o;
return this;
}
/**
* Creates a criterion using the <= operator
*
* @param o
* @return the criteria
*/
public Criteria lte(Object o) {
this.criteriaType = CriteriaType.LTE;
this.expected = o;
return this;
}
/**
* Creates a criterion using the > operator
*
* @param o
* @return the criteria
*/
public Criteria gt(Object o) {
this.criteriaType = CriteriaType.GT;
this.expected = o;
return this;
}
/**
* Creates a criterion using the >= operator
*
* @param o
* @return the criteria
*/
public Criteria gte(Object o) {
this.criteriaType = CriteriaType.GTE;
this.expected = o;
return this;
}
/**
* Creates a criterion using a Regex
*
* @param pattern
* @return the criteria
*/
public Criteria regex(Pattern pattern) {
notNull(pattern, "pattern can not be null");
this.criteriaType = CriteriaType.REGEX;
this.expected = pattern;
return this;
}
/**
* The in
operator is analogous to the SQL IN modifier, allowing you
* to specify an array of possible matches.
*
* @param o the values to match against
* @return the criteria
*/
public Criteria in(Object... o) {
return in(Arrays.asList(o));
}
/**
* The in
operator is analogous to the SQL IN modifier, allowing you
* to specify an array of possible matches.
*
* @param c the collection containing the values to match against
* @return the criteria
*/
public Criteria in(Collection> c) {
notNull(c, "collection can not be null");
this.criteriaType = CriteriaType.IN;
this.expected = c;
return this;
}
/**
* The nin
operator is similar to $in except that it selects objects for
* which the specified field does not have any value in the specified array.
*
* @param o the values to match against
* @return the criteria
*/
public Criteria nin(Object... o) {
return nin(Arrays.asList(o));
}
/**
* The nin
operator is similar to $in except that it selects objects for
* which the specified field does not have any value in the specified array.
*
* @param c the values to match against
* @return the criteria
*/
public Criteria nin(Collection> c) {
notNull(c, "collection can not be null");
this.criteriaType = CriteriaType.NIN;
this.expected = c;
return this;
}
/**
* The all
operator is similar to $in, but instead of matching any value
* in the specified array all values in the array must be matched.
*
* @param o
* @return the criteria
*/
public Criteria all(Object... o) {
return all(Arrays.asList(o));
}
/**
* The all
operator is similar to $in, but instead of matching any value
* in the specified array all values in the array must be matched.
*
* @param c
* @return the criteria
*/
public Criteria all(Collection> c) {
notNull(c, "collection can not be null");
this.criteriaType = CriteriaType.ALL;
this.expected = c;
return this;
}
/**
* The size
operator matches:
*
*
* - array with the specified number of elements.
* - string with given length.
*
*
* @param size
* @return the criteria
*/
public Criteria size(int size) {
this.criteriaType = CriteriaType.SIZE;
this.expected = size;
return this;
}
/**
* Check for existence (or lack thereof) of a field.
*
* @param b
* @return the criteria
*/
public Criteria exists(boolean b) {
this.criteriaType = CriteriaType.EXISTS;
this.expected = b;
return this;
}
/**
* The $type operator matches values based on their Java type.
*
* @param t
* @return the criteria
*/
public Criteria type(Class> t) {
notNull(t, "type can not be null");
this.criteriaType = CriteriaType.TYPE;
this.expected = t;
return this;
}
/**
* The notEmpty
operator checks that an array or String is not empty.
*
* @return the criteria
*/
public Criteria notEmpty() {
this.criteriaType = CriteriaType.NOT_EMPTY;
this.expected = null;
return this;
}
/**
* The matches
operator checks that an object matches the given predicate.
*
* @param p
* @return the criteria
*/
public Criteria matches(Predicate p) {
this.criteriaType = CriteriaType.MATCHES;
this.expected = p;
return this;
}
/**
* Creates a new criteria
* @param path path to evaluate in criteria
* @param operator operator
* @param expected expected value
* @return a new Criteria
*/
public static Criteria create(String path, String operator, String expected) {
if (!expected.isEmpty() && expected.charAt(0) == '\'' && expected.charAt(expected.length() - 1) == '\'') {
expected = expected.substring(1, expected.length() - 1);
}
Path p = PathCompiler.compile(path);
if (("$".equals(path) || "@".equals(path) )&& (operator == null || operator.isEmpty()) && (expected == null || expected.isEmpty())) {
return new Criteria(p, CriteriaType.NE, null);
} else if (operator.isEmpty()) {
return Criteria.where(path).exists(true);
} else {
if(expected.startsWith("$") || expected.startsWith("@")){
Path compile = PathCompiler.compile(expected);
if(!compile.isDefinite()){
throw new InvalidPathException("the predicate path: " + expected + " is not definite");
}
return new Criteria(p, CriteriaType.parse(operator), compile);
} else {
return new Criteria(p, CriteriaType.parse(operator), expected);
}
}
}
private static int safeCompare(Object expected, Object providerParsed) throws ValueCompareException {
if(expected == providerParsed){
return 0;
}
boolean expNullish = isNullish(expected);
boolean provNullish = isNullish(providerParsed);
if (expNullish && !provNullish) {
return -1;
} else if (!expNullish && provNullish) {
return 1;
} else if (expNullish && provNullish) {
return 0;
} else if (expected instanceof String && providerParsed instanceof String) {
return ((String) expected).compareTo((String) providerParsed);
} else if (expected instanceof Number && providerParsed instanceof Number) {
return new BigDecimal(expected.toString()).compareTo(new BigDecimal(providerParsed.toString()));
} else if (expected instanceof String && providerParsed instanceof Number) {
return new BigDecimal(expected.toString()).compareTo(new BigDecimal(providerParsed.toString()));
} else if (expected instanceof String && providerParsed instanceof Boolean) {
Boolean e = Boolean.valueOf((String) expected);
Boolean a = (Boolean) providerParsed;
return e.compareTo(a);
} else if (expected instanceof Boolean && providerParsed instanceof Boolean) {
Boolean e = (Boolean) expected;
Boolean a = (Boolean) providerParsed;
return e.compareTo(a);
} else {
logger.debug("Can not compare a {} with a {}", expected.getClass().getName(), providerParsed.getClass().getName());
throw new ValueCompareException();
}
}
private static boolean isNullish(Object o) {
return (o == null || ((o instanceof String) && ("null".equals(o))));
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(path.toString())
.append("|")
.append(criteriaType.name())
.append("|")
.append(expected)
.append("|");
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy