Browse Source

Merge branch 'master' into use-json-smart-for-query-parsing

pull/314/head
jochenberger 8 years ago committed by GitHub
parent
commit
2a16517f61
  1. 2
      README.md
  2. 4
      build.gradle
  3. BIN
      gradle/wrapper/gradle-wrapper.jar
  4. 4
      gradle/wrapper/gradle-wrapper.properties
  5. 68
      gradlew
  6. 12
      gradlew.bat
  7. 2
      json-path-assert/build.gradle
  8. 16
      json-path-web-test/build.gradle
  9. 18
      json-path/build.gradle
  10. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/JsonContext.java
  11. 15
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
  12. 10
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/LogicalExpressionNode.java
  13. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/LogicalOperator.java
  14. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
  15. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java
  16. 48
      json-path/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java
  17. 4
      json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java
  18. 61
      json-path/src/test/java/com/jayway/jsonpath/GsonJsonProviderTest.java

2
README.md

@ -256,6 +256,8 @@ List<Map<String, Object>> books = JsonPath.parse(json)
You can use `&&` and `||` to combine multiple predicates `[?(@.price < 10 && @.category == 'fiction')]` ,
`[?(@.category == 'reference' || @.price > 10)]`.
You can use `!` to negate a predicate `[?(!(@.price < 10 && @.category == 'fiction'))]`.
###Filter Predicates
Predicates can be built using the Filter API as shown below:

4
build.gradle

@ -17,7 +17,7 @@ ext {
gson: 'com.google.code.gson:gson:2.3.1',
jettison: 'org.codehaus.jettison:jettison:1.3.7',
jsonOrg: 'org.json:json:20140107',
tapestryJson: 'org.apache.tapestry:tapestry-json:5.4.0',
tapestryJson: 'org.apache.tapestry:tapestry-json:5.4.1',
hamcrestCore: 'org.hamcrest:hamcrest-core:1.3',
hamcrestLibrary: 'org.hamcrest:hamcrest-library:1.3',
@ -73,7 +73,7 @@ subprojects {
}
task wrapper(type: Wrapper) {
gradleVersion = '2.11'
gradleVersion = '3.4.1'
}
//Task used by Heroku for staging

BIN
gradle/wrapper/gradle-wrapper.jar vendored

Binary file not shown.

4
gradle/wrapper/gradle-wrapper.properties vendored

@ -1,6 +1,6 @@
#Fri Feb 12 10:36:51 CET 2016
#Fri Mar 17 10:32:10 CET 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-bin.zip

68
gradlew vendored

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
##############################################################################
##
@ -6,12 +6,30 @@
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@ -30,6 +48,7 @@ die ( ) {
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
@ -40,26 +59,11 @@ case "`uname`" in
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@ -85,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@ -150,11 +154,19 @@ if $cygwin ; then
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save ( ) {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
exec "$JAVACMD" "$@"

12
gradlew.bat vendored

@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@ -49,7 +49,6 @@ goto fail
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line

2
json-path-assert/build.gradle

@ -1,7 +1,5 @@
apply from: "$rootDir/gradle/publishMaven.gradle"
displayName = "JsonPath Assert"
description = "Assertions on Json using JsonPath"
jar {

16
json-path-web-test/build.gradle

@ -1,18 +1,18 @@
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'application'
displayName = "JsonPath Test Bench"
description = "Web app that compares different JsonPath implementations."
mainClassName = 'com.jayway.jsonpath.web.boot.Main'
task createBuildInfoFile << {
def buildInfoFile = new File("$buildDir/classes/main/build-info.properties")
Properties props = new Properties()
props.setProperty('version', project.version.toString())
props.setProperty('timestamp', project.buildTimestamp)
props.store(buildInfoFile.newWriter(), null)
task createBuildInfoFile {
doLast {
def buildInfoFile = new File("$buildDir/classes/main/build-info.properties")
Properties props = new Properties()
props.setProperty('version', project.version.toString())
props.setProperty('timestamp', project.buildTimestamp)
props.store(buildInfoFile.newWriter(), null)
}
}
jar {

18
json-path/build.gradle

@ -1,20 +1,20 @@
apply from: "$rootDir/gradle/publishMaven.gradle"
displayName = "Json Path"
description = "Java port of Stefan Goessner JsonPath."
jar {
baseName 'json-path'
manifest {
attributes 'Implementation-Title': 'json-path', 'Implementation-Version': version
instruction 'Import-Package', 'org.json.*;resolution:=optional', 'com.google.gson.*;resolution:=optional', 'com.fasterxml.jackson.*;resolution:=optional', 'org.apache.tapestry5.json.*;resolution:=optional', '*'
instruction 'Import-Package', 'org.json.*;resolution:=optional', 'com.google.gson.*;resolution:=optional', 'com.fasterxml.jackson.*;resolution:=optional', 'org.apache.tapestry5.json.*;resolution:=optional', 'org.codehaus.jettison.*;resolution:=optional', '*'
}
}
dependencies {
compile libs.jsonSmart
compile (libs.jsonSmart){
// see https://github.com/jayway/JsonPath/issues/228, https://github.com/netplex/json-smart-v2/issues/20
exclude group: 'org.ow2.asm', module: 'asm'
}
compile libs.slf4jApi
compile libs.jacksonDatabind, optional
compile libs.gson, optional
@ -39,7 +39,7 @@ task distZip(type: Zip, dependsOn: assemble) {
}
from(project.configurations.compile) {
into 'lib'
exclude { it.file.name.contains('gson') || it.file.name.contains('jackson') || it.file.name.contains('json-2') || it.file.name.contains('jettison') }
exclude { it.file.name.contains('gson') || it.file.name.contains('jackson') || it.file.name.contains('json-2') || it.file.name.contains('jettison') || it.file.name.contains('tapestry') }
}
from(project.configurations.compile) {
into 'lib-optional/jackson'
@ -58,7 +58,7 @@ task distZip(type: Zip, dependsOn: assemble) {
include { it.file.name.contains('json-2') }
}
from(project.configurations.compile) {
into 'lib-optional/jsonOrg'
into 'lib-optional/tapestry'
include { it.file.name.contains('tapestry') }
}
}
@ -79,7 +79,7 @@ task distTar(type: Tar, dependsOn: assemble) {
}
from(project.configurations.compile) {
into 'lib'
exclude { it.file.name.contains('gson') || it.file.name.contains('jackson') || it.file.name.contains('json-2') || it.file.name.contains('jettison') }
exclude { it.file.name.contains('gson') || it.file.name.contains('jackson') || it.file.name.contains('json-2') || it.file.name.contains('jettison') || it.file.name.contains('tapestry') }
}
from(project.configurations.compile) {
into 'lib-optional/jackson'
@ -98,7 +98,7 @@ task distTar(type: Tar, dependsOn: assemble) {
include { it.file.name.contains('json-2') }
}
from(project.configurations.compile) {
into 'lib-optional/jsonOrg'
into 'lib-optional/tapestry'
include { it.file.name.contains('tapestry') }
}
}

2
json-path/src/main/java/com/jayway/jsonpath/internal/JsonContext.java

@ -289,7 +289,7 @@ public class JsonContext implements ParseContext, DocumentContext {
return this;
}
private final class LimitingEvaluationListener implements EvaluationListener {
private final static class LimitingEvaluationListener implements EvaluationListener {
final int limit;
private LimitingEvaluationListener(int limit) {

15
json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java

@ -120,7 +120,7 @@ public class FilterCompiler {
/*
* LogicalOR = LogicalAND { '||' LogicalAND }
* LogicalAND = LogicalANDOperand { '&&' LogicalANDOperand }
* LogicalANDOperand = RelationalExpression | '(' LogicalOR ')'
* LogicalANDOperand = RelationalExpression | '(' LogicalOR ')' | '!' LogicalANDOperand
* RelationalExpression = Value [ RelationalOperator Value ]
*/
@ -164,6 +164,19 @@ public class FilterCompiler {
}
private ExpressionNode readLogicalANDOperand() {
int savepoint = filter.skipBlanks().position();
if (filter.skipBlanks().currentCharIs(NOT)) {
filter.readSignificantChar(NOT);
switch (filter.skipBlanks().currentChar()) {
case DOC_CONTEXT:
case EVAL_CONTEXT:
filter.setPosition(savepoint);
break;
default:
final ExpressionNode op = readLogicalANDOperand();
return LogicalExpressionNode.createLogicalNot(op);
}
}
if (filter.skipBlanks().currentCharIs(OPEN_PARENTHESIS)) {
filter.readSignificantChar(OPEN_PARENTHESIS);
final ExpressionNode op = readLogicalOR();

10
json-path/src/main/java/com/jayway/jsonpath/internal/filter/LogicalExpressionNode.java

@ -10,6 +10,10 @@ public class LogicalExpressionNode extends ExpressionNode {
protected List<ExpressionNode> chain = new ArrayList<ExpressionNode>();
private final LogicalOperator operator;
public static ExpressionNode createLogicalNot(ExpressionNode op) {
return new LogicalExpressionNode(op, LogicalOperator.NOT, null);
}
public static LogicalExpressionNode createLogicalOr(ExpressionNode left,ExpressionNode right){
return new LogicalExpressionNode(left, LogicalOperator.OR, right);
}
@ -68,13 +72,17 @@ public class LogicalExpressionNode extends ExpressionNode {
}
}
return false;
} else {
} else if (operator == LogicalOperator.AND) {
for (ExpressionNode expression : chain) {
if(!expression.apply(ctx)){
return false;
}
}
return true;
} else {
ExpressionNode expression = chain.get(0);
return !expression.apply(ctx);
}
}
}

2
json-path/src/main/java/com/jayway/jsonpath/internal/filter/LogicalOperator.java

@ -5,6 +5,7 @@ import com.jayway.jsonpath.InvalidPathException;
public enum LogicalOperator {
AND("&&"),
NOT("!"),
OR("||");
private final String operatorString;
@ -24,6 +25,7 @@ public enum LogicalOperator {
public static LogicalOperator fromString(String operatorString){
if(AND.operatorString.equals(operatorString)) return AND;
else if(NOT.operatorString.equals(operatorString)) return NOT;
else if(OR.operatorString.equals(operatorString)) return OR;
else throw new InvalidPathException("Failed to parse operator " + operatorString);
}

2
json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java

@ -810,9 +810,9 @@ public abstract class ValueNode {
}
public ValueNode evaluate(Predicate.PredicateContext ctx) {
Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options(Option.REQUIRE_PROPERTIES).build();
if (isExistsCheck()) {
try {
Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options(Option.REQUIRE_PROPERTIES).build();
Object result = path.evaluate(ctx.item(), ctx.root(), c).getValue(false);
return result == JsonProvider.UNDEFINED ? ValueNode.FALSE : ValueNode.TRUE;
} catch (PathNotFoundException e) {

2
json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java

@ -163,7 +163,7 @@ public class EvaluationContextImpl implements EvaluationContext {
return res;
}
private class FoundResultImpl implements EvaluationListener.FoundResult {
private static class FoundResultImpl implements EvaluationListener.FoundResult {
private final int index;
private final String path;

48
json-path/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java

@ -14,28 +14,25 @@
*/
package com.jayway.jsonpath.spi.json;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.internal.LazilyParsedNumber;
import com.jayway.jsonpath.InvalidJsonException;
import com.jayway.jsonpath.JsonPathException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class GsonJsonProvider extends AbstractJsonProvider {
private static final JsonParser PARSER = new JsonParser();
@ -86,25 +83,38 @@ public class GsonJsonProvider extends AbstractJsonProvider {
return o;
}
private static boolean isPrimitiveNumber(final Number n) {
return n instanceof Integer ||
n instanceof Double ||
n instanceof Long ||
n instanceof BigDecimal ||
n instanceof BigInteger;
}
private static Number unwrapNumber(final Number n) {
Number unwrapped;
if (n instanceof LazilyParsedNumber) {
LazilyParsedNumber lpn = (LazilyParsedNumber) n;
BigDecimal bigDecimal = new BigDecimal(lpn.toString());
if (!isPrimitiveNumber(n)) {
BigDecimal bigDecimal = new BigDecimal(n.toString());
if (bigDecimal.scale() <= 0) {
if (bigDecimal.compareTo(new BigDecimal(Integer.MAX_VALUE)) <= 0) {
unwrapped = bigDecimal.intValue();
} else {
} else if (bigDecimal.compareTo(new BigDecimal(Long.MAX_VALUE)) <= 0){
unwrapped = bigDecimal.longValue();
} else {
unwrapped = bigDecimal;
}
} else {
unwrapped = bigDecimal.doubleValue();
final double doubleValue = bigDecimal.doubleValue();
if (BigDecimal.valueOf(doubleValue).compareTo(bigDecimal) != 0) {
unwrapped = bigDecimal;
} else {
unwrapped = doubleValue;
}
}
} else {
unwrapped = n;
}
return unwrapped;
}

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

@ -1,6 +1,6 @@
package com.jayway.jsonpath.spi.json;
import com.google.gson.JsonObject;
import org.json.JSONObject;
import com.jayway.jsonpath.InvalidJsonException;
import com.jayway.jsonpath.JsonPathException;
import org.json.JSONArray;
@ -62,7 +62,7 @@ public class JsonOrgJsonProvider extends AbstractJsonProvider {
@Override
public Object createMap() {
return new JsonObject();
return new JSONObject();
}
@Override

61
json-path/src/test/java/com/jayway/jsonpath/GsonJsonProviderTest.java

@ -7,6 +7,8 @@ import com.jayway.jsonpath.spi.mapper.MappingException;
import org.junit.Test;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import static com.jayway.jsonpath.JsonPath.using;
@ -69,6 +71,65 @@ public class GsonJsonProviderTest extends BaseTest {
assertThat(val).isEqualTo(node.getAsLong());
}
@Test
public void doubles_are_unwrapped() {
final String json = "{double-property = 56.78}";
JsonElement node = using(GSON_CONFIGURATION).parse(json).read("$.double-property");
Double val = using(GSON_CONFIGURATION).parse(json).read("$.double-property", Double.class);
assertThat(val).isEqualTo(56.78);
assertThat(val).isEqualTo(node.getAsDouble());
}
@Test
public void bigdecimals_are_unwrapped() {
final BigDecimal bd = BigDecimal.valueOf(Long.MAX_VALUE).add(BigDecimal.valueOf(10.5));
final String json = "{bd-property = " + bd.toString() + "}";
JsonElement node = using(GSON_CONFIGURATION).parse(json).read("$.bd-property");
BigDecimal val = using(GSON_CONFIGURATION).parse(json).read("$.bd-property", BigDecimal.class);
assertThat(val).isEqualTo(bd);
assertThat(val).isEqualTo(node.getAsBigDecimal());
}
@Test
public void small_bigdecimals_are_unwrapped() {
final BigDecimal bd = BigDecimal.valueOf(10.5);
final String json = "{bd-property = " + bd.toString() + "}";
JsonElement node = using(GSON_CONFIGURATION).parse(json).read("$.bd-property");
BigDecimal val = using(GSON_CONFIGURATION).parse(json).read("$.bd-property", BigDecimal.class);
assertThat(val).isEqualTo(bd);
assertThat(val).isEqualTo(node.getAsBigDecimal());
}
@Test
public void bigintegers_are_unwrapped() {
final BigInteger bi = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.TEN);
final String json = "{bi-property = " + bi.toString() + "}";
JsonElement node = using(GSON_CONFIGURATION).parse(json).read("$.bi-property");
BigInteger val = using(GSON_CONFIGURATION).parse(json).read("$.bi-property", BigInteger.class);
assertThat(val).isEqualTo(bi);
assertThat(val).isEqualTo(node.getAsBigInteger());
}
@Test
public void small_bigintegers_are_unwrapped() {
final BigInteger bi = BigInteger.valueOf(Long.MAX_VALUE);
final String json = "{bi-property = " + bi.toString() + "}";
JsonElement node = using(GSON_CONFIGURATION).parse(json).read("$.bi-property");
BigInteger val = using(GSON_CONFIGURATION).parse(json).read("$.bi-property", BigInteger.class);
assertThat(val).isEqualTo(bi);
assertThat(val).isEqualTo(node.getAsBigInteger());
}
@Test
public void int_to_long_mapping() {
assertThat(using(GSON_CONFIGURATION).parse("{\"val\": 1}").read("val", Long.class)).isEqualTo(1L);

Loading…
Cancel
Save