Skip to content

Commit

Permalink
Merge pull request #115 from genman/patch
Browse files Browse the repository at this point in the history
Support for JSON expressions parsing, fix escapes in strings.
  • Loading branch information
kallestenflo committed Oct 1, 2015
2 parents 35b45f5 + fb9c621 commit fca1284
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public class JsonAssertTest {
" \"bicycle\": {\n" +
" \"color\": \"red\",\n" +
" \"price\": 19.95\n," +
" \"gears\": [23, 50]\n," +
" \"extra\": {\"x\": 0}\n," +
" \"escape\" : \"Esc\\b\\f\\n\\r\\t\\u002A\",\n" +
" \"nullValue\": null\n" +
" }\n" +
" }\n" +
Expand All @@ -55,6 +58,14 @@ public void has_path() throws Exception {
with(JSON).assertNotDefined("$.store.bicycle[?(@.color == 'red' )]");
}

@Test
public void assert_gears() throws Exception {
with(JSON).assertThat("$.store.bicycle[?(@.gears == [23, 50])]", is(collectionWithSize(equalTo(1))));
with(JSON).assertThat("$.store.bicycle[?(@.gears == [23, 77])]", is(collectionWithSize(equalTo(0))));
with(JSON).assertThat("$.store.bicycle[?(@.extra == {\"x\":0})]", is(collectionWithSize(equalTo(1))));
with(JSON).assertThat("$.store.bicycle[?(@.escape == 'Esc\\b\\f\\n\\r\\t\\u002A')]", is(collectionWithSize(equalTo(1))));
}

@Test(expected = AssertionError.class)
public void failed_error_message() throws Exception {

Expand Down
86 changes: 78 additions & 8 deletions json-path/src/main/java/com/jayway/jsonpath/Criteria.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.PathCompiler;
import com.jayway.jsonpath.internal.token.PredicateContextImpl;
import com.jayway.jsonpath.spi.json.JsonProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -48,6 +49,7 @@ public class Criteria implements Predicate {
CriteriaType.GT.toString(),
CriteriaType.REGEX.toString()
};
private static final char BS = '\\';

private Object left;
private CriteriaType criteriaType;
Expand All @@ -59,7 +61,7 @@ private static enum CriteriaType {
EQ {
@Override
boolean eval(Object expected, Object model, PredicateContext ctx) {
boolean res = (0 == safeCompare(expected, model));
boolean res = (0 == safeCompare(expected, model, ctx));
if (logger.isDebugEnabled()) logger.debug("[{}] {} [{}] => {}", model, name(), expected, res);
return res;
}
Expand All @@ -72,7 +74,7 @@ public String toString() {
NE {
@Override
boolean eval(Object expected, Object model, PredicateContext ctx) {
boolean res = (0 != safeCompare(expected, model));
boolean res = (0 != safeCompare(expected, model, ctx));
if (logger.isDebugEnabled()) logger.debug("[{}] {} [{}] => {}", model, name(), expected, res);
return res;
}
Expand Down Expand Up @@ -710,6 +712,14 @@ private static boolean isString(String string) {
return (string != null && !string.isEmpty() && string.charAt(0) == '\'' && string.charAt(string.length() - 1) == '\'');
}

private static boolean isJson(String string) {
if (string == null || string.length() <= 1)
return false;
char c0 = string.charAt(0);
char c1 = string.charAt(string.length() - 1);
return (c0 == '[' && c1 == ']') || (c0 == '{' && c1 == '}');
}

private static boolean isPattern(String string) {
return (string != null
&& !string.isEmpty()
Expand All @@ -727,7 +737,6 @@ private static Pattern compilePattern(String string) {
return Pattern.compile(regex, flags);
}


/**
* Parse the provided criteria
*
Expand Down Expand Up @@ -755,6 +764,28 @@ public static Criteria parse(String criteria) {
return Criteria.create(left, operator, right);
}

/**
* Wrapper for JSON to be parsed as a String.
*/
private static class JsonValue {
final String value;
volatile Object jsonValue;
JsonValue(String value) { this.value = value; }

Object parsed(PredicateContext ctx) {
if (jsonValue == null) {
JsonProvider provider = ctx.configuration().jsonProvider();
jsonValue = provider.parse(value);
}
return jsonValue;
}

@Override
public String toString() {
return getClass().getSimpleName() + " " + value;
}
}

/**
* Creates a new criteria
*
Expand Down Expand Up @@ -797,6 +828,8 @@ public static Criteria create(String left, String operator, String right) {
rightPrepared = rightPath;
} else if (isString(right)) {
rightPrepared = right.substring(1, right.length() - 1);
} else if (isJson(right)) {
rightPrepared = new JsonValue(right);
} else if (isPattern(right)) {
rightPrepared = compilePattern(right);
}
Expand All @@ -809,6 +842,42 @@ public static Criteria create(String left, String operator, String right) {
}

private static int safeCompare(Object left, Object right) throws ValueCompareException {
return safeCompare(left, right, null);
}

private static String unescape(String s) {
if (s.indexOf(BS) == - 1)
return s;
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == BS) {
char c2 = s.charAt(++i);
switch (c2) {
case 'b': c2 = '\b'; break;
case 'f': c2 = '\f'; break;
case 'n': c2 = '\n'; break;
case 'r': c2 = '\r'; break;
case 't': c2 = '\t'; break;
case 'u':
try {
String hex = s.substring(i + 1, i + 5);
c2 = (char)Integer.parseInt(hex, 16);
i += 4;
} catch (Exception e) {
throw new ValueCompareException("\\u parse failed", e);
}
break;
}
sb.append(c2);
} else {
sb.append(c);
}
}
return sb.toString();
}

private static int safeCompare(Object left, Object right, PredicateContext ctx) throws ValueCompareException {

if (left == right) {
return 0;
Expand All @@ -824,11 +893,8 @@ private static int safeCompare(Object left, Object right) throws ValueCompareExc
} else if (leftNullish && rightNullish) {
return 0;
} else if (left instanceof String && right instanceof String) {
String exp = (String) left;
if (exp.contains("\'")) {
exp = exp.replace("\\'", "'");
}
return exp.compareTo((String) right);
String expected = unescape((String) left);
return expected.compareTo((String) right);
} else if (left instanceof Number && right instanceof Number) {
return new BigDecimal(left.toString()).compareTo(new BigDecimal(right.toString()));
} else if (left instanceof String && right instanceof Number) {
Expand All @@ -841,6 +907,10 @@ private static int safeCompare(Object left, Object right) throws ValueCompareExc
Boolean e = (Boolean) left;
Boolean a = (Boolean) right;
return e.compareTo(a);
} else if (left instanceof JsonValue) {
notNull(ctx, "ctx");
JsonValue json = (JsonValue) left;
return right.equals(json.parsed(ctx)) ? 0 : -1;
} else {
logger.debug("Can not compare a {} with a {}", left.getClass().getName(), right.getClass().getName());
throw new ValueCompareException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@
package com.jayway.jsonpath;

public class ValueCompareException extends JsonPathException {
}

public ValueCompareException() {
}

public ValueCompareException(String message) {
super(message);
}

public ValueCompareException(String message, Throwable cause) {
super(message, cause);
}

}
1 change: 1 addition & 0 deletions json-path/src/test/java/com/jayway/jsonpath/BaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public class BaseTest {
" ],\n" +
" \"bicycle\" : {\n" +
" \"foo\" : \"baz\",\n" +
" \"escape\" : \"Esc\\b\\f\\n\\r\\t\\n\\t\\u002A\",\n" +
" \"color\" : \"red\",\n" +
" \"display-price\" : 19.95,\n" +
" \"foo:bar\" : \"fooBar\",\n" +
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
Expand Up @@ -240,6 +240,26 @@ public void string_regex_evals() {
assertThat(filter(where("int-key").regex(Pattern.compile("^string$"))).apply(createPredicateContext(json))).isEqualTo(false);
}

//----------------------------------------------------------------------------
//
// JSON equality
//
//----------------------------------------------------------------------------
@Test
public void json_evals() {
String nest = "{\"a\":true}";
String arr = "[1,2]";
String json = "{\"foo\":" + arr + ", \"bar\":" + nest + "}";
Object tree = Configuration.defaultConfiguration().jsonProvider().parse(json);
Predicate.PredicateContext context = createPredicateContext(tree);
Filter farr = Filter.parse("[?(@.foo == " + arr + ")]");
Filter fobjF = Filter.parse("[?(@.foo == " + nest + ")]");
Filter fobjT = Filter.parse("[?(@.bar == " + nest + ")]");
assertThat(farr.apply(context)).isEqualTo(true);
assertThat(fobjF.apply(context)).isEqualTo(false);
assertThat(fobjT.apply(context)).isEqualTo(true);
}

//----------------------------------------------------------------------------
//
// IN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public class InlineFilterTest extends BaseTest {

private static ReadContext reader = JsonPath.parse(JSON_DOCUMENT);
private static int bookCount = 4;

@Test
public void root_context_can_be_referred_in_predicate() {
Expand All @@ -23,13 +24,13 @@ public void root_context_can_be_referred_in_predicate() {
public void multiple_context_object_can_be_refered() {

List all = reader.read("store.book[ ?(@.category == @.category) ]", List.class);
assertThat(all.size()).isEqualTo(4);
assertThat(all.size()).isEqualTo(bookCount);

List all2 = reader.read("store.book[ ?(@.category == @['category']) ]", List.class);
assertThat(all2.size()).isEqualTo(4);
assertThat(all2.size()).isEqualTo(bookCount);

List all3 = reader.read("store.book[ ?(@ == @) ]", List.class);
assertThat(all3.size()).isEqualTo(4);
assertThat(all3.size()).isEqualTo(bookCount);

List none = reader.read("store.book[ ?(@.category != @.category) ]", List.class);
assertThat(none.size()).isEqualTo(0);
Expand Down

0 comments on commit fca1284

Please sign in to comment.