Browse Source

Merge branch 'master' into patch-3

pull/317/head
jochenberger 8 years ago committed by GitHub
parent
commit
0768e5bd0f
  1. 2
      LICENSE
  2. 3
      README.md
  3. 6
      build.gradle
  4. BIN
      gradle/wrapper/gradle-wrapper.jar
  5. 4
      gradle/wrapper/gradle-wrapper.properties
  6. 68
      gradlew
  7. 12
      gradlew.bat
  8. 2
      json-path-assert/build.gradle
  9. 16
      json-path-web-test/build.gradle
  10. 13
      json-path/build.gradle
  11. 36
      json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java
  12. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/JsonContext.java
  13. 33
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
  14. 10
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/LogicalExpressionNode.java
  15. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/LogicalOperator.java
  16. 6
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
  17. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexOperation.java
  18. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java
  19. 25
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
  20. 3
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
  21. 48
      json-path/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java
  22. 8
      json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java
  23. 7
      json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
  24. 61
      json-path/src/test/java/com/jayway/jsonpath/GsonJsonProviderTest.java
  25. 8
      json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java
  26. 128
      json-path/src/test/java/com/jayway/jsonpath/ProviderInTest.java
  27. 20
      json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java
  28. 9
      json-path/src/test/java/com/jayway/jsonpath/old/internal/ArrayIndexFilterTest.java

2
LICENSE

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2017 Jayway
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

3
README.md

@ -155,6 +155,7 @@ Given the json
| <a href="http://jsonpath.herokuapp.com/?path=$.store.*" target="_blank">$.store.*</a> | All things, both books and bicycles |
| <a href="http://jsonpath.herokuapp.com/?path=$.store..price" target="_blank">$.store..price</a> | The price of everything |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[2]" target="_blank">$..book[2]</a> | The third book |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[2]" target="_blank">$..book[-2]</a> | The second to last book |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[0,1]" target="_blank">$..book[0,1]</a> | The first two books |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[:2]" target="_blank">$..book[:2]</a> | All books from index 0 (inclusive) until index 2 (exclusive) |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[1:2]" target="_blank">$..book[1:2]</a> | All books from index 1 (inclusive) until index 2 (exclusive) |
@ -256,6 +257,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:

6
build.gradle

@ -12,12 +12,12 @@ buildscript {
ext {
libs = [
slf4jApi: 'org.slf4j:slf4j-api:1.7.25',
jsonSmart: 'net.minidev:json-smart:2.2.1',
jsonSmart: 'net.minidev:json-smart:2.3',
jacksonDatabind: 'com.fasterxml.jackson.core:jackson-databind:2.6.3',
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 {

13
json-path/build.gradle

@ -1,15 +1,12 @@
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', 'net.minidev.json.*;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', 'net.minidev.json.*;resolution:=optional', 'org.apache.tapestry5.json.*;resolution:=optional', 'org.codehaus.jettison.*;resolution:=optional', '*'
}
}
@ -39,7 +36,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 +55,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 +76,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 +95,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') }
}
}

36
json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java

@ -17,14 +17,16 @@ public class CharacterIndex {
private final CharSequence charSequence;
private int position;
private int endPosition;
public CharacterIndex(CharSequence charSequence) {
this.charSequence = charSequence;
this.position = 0;
this.endPosition = charSequence.length() - 1;
}
public int length() {
return charSequence.length();
return endPosition + 1;
}
public char charAt(int idx) {
@ -39,6 +41,10 @@ public class CharacterIndex {
return (charSequence.charAt(position) == c);
}
public boolean lastCharIs(char c) {
return charSequence.charAt(endPosition) == c;
}
public boolean nextCharIs(char c) {
return inBounds(position + 1) && (charSequence.charAt(position + 1) == c);
}
@ -47,12 +53,21 @@ public class CharacterIndex {
return setPosition(position + charCount);
}
public int decrementEndPosition(int charCount) {
return setEndPosition(endPosition - charCount);
}
public int setPosition(int newPosition) {
//position = min(newPosition, charSequence.length() - 1);
position = newPosition;
return position;
}
private int setEndPosition(int newPosition) {
endPosition = newPosition;
return endPosition;
}
public int position(){
return position;
}
@ -244,7 +259,7 @@ public class CharacterIndex {
}
public boolean currentIsTail() {
return position >= charSequence.length()-1;
return position >= endPosition;
}
public boolean hasMoreCharacters() {
@ -252,7 +267,7 @@ public class CharacterIndex {
}
public boolean inBounds(int idx) {
return (idx >= 0) && (idx < charSequence.length());
return (idx >= 0) && (idx <= endPosition);
}
public boolean inBounds() {
return inBounds(position);
@ -281,9 +296,22 @@ public class CharacterIndex {
}
public CharacterIndex skipBlanks() {
while (inBounds() && currentChar() == SPACE){
while (inBounds() && position < endPosition && currentChar() == SPACE){
incrementPosition(1);
}
return this;
}
private CharacterIndex skipBlanksAtEnd() {
while (inBounds() && position < endPosition && lastCharIs(SPACE)){
decrementEndPosition(1);
}
return this;
}
public CharacterIndex trim() {
skipBlanks();
skipBlanksAtEnd();
return this;
}
}

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) {

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

@ -54,20 +54,22 @@ public class FilterCompiler {
}
private FilterCompiler(String filterString) {
filterString = filterString.trim();
if (!filterString.startsWith("[") || !filterString.endsWith("]")) {
filter = new CharacterIndex(filterString);
filter.trim();
if (!filter.currentCharIs('[') || !filter.lastCharIs(']')) {
throw new InvalidPathException("Filter must start with '[' and end with ']'. " + filterString);
}
filterString = filterString.substring(1, filterString.length() - 1).trim();
if (!filterString.startsWith("?")) {
filter.incrementPosition(1);
filter.decrementEndPosition(1);
filter.trim();
if (!filter.currentCharIs('?')) {
throw new InvalidPathException("Filter must start with '[?' and end with ']'. " + filterString);
}
filterString = filterString.substring(1).trim();
if (!filterString.startsWith("(") || !filterString.endsWith(")")) {
filter.incrementPosition(1);
filter.trim();
if (!filter.currentCharIs('(') || !filter.lastCharIs(')')) {
throw new InvalidPathException("Filter must start with '[?(' and end with ')]'. " + filterString);
}
filter = new CharacterIndex(filterString);
}
public Predicate compile() {
@ -120,7 +122,7 @@ public class FilterCompiler {
/*
* LogicalOR = LogicalAND { '||' LogicalAND }
* LogicalAND = LogicalANDOperand { '&&' LogicalANDOperand }
* LogicalANDOperand = RelationalExpression | '(' LogicalOR ')'
* LogicalANDOperand = RelationalExpression | '(' LogicalOR ')' | '!' LogicalANDOperand
* RelationalExpression = Value [ RelationalOperator Value ]
*/
@ -164,6 +166,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);
}

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

@ -154,7 +154,7 @@ public abstract class ValueNode {
if ((c0 == '[' && c1 == ']') || (c0 == '{' && c1 == '}')){
try {
Configuration.defaultConfiguration().jsonProvider().parse(str);
return false;
return true;
} catch(Exception e){
return false;
}
@ -805,9 +805,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) {
@ -842,4 +842,4 @@ public abstract class ValueNode {
}
}
}

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

@ -42,7 +42,7 @@ public class ArrayIndexOperation {
//check valid chars
for (int i = 0; i < operation.length(); i++) {
char c = operation.charAt(i);
if (!isDigit(c) && c != ',' && c != ' ') {
if (!isDigit(c) && c != ',' && c != ' ' && c != '-') {
throw new InvalidPathException("Failed to parse ArrayIndexOperation: " + operation);
}
}

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;

25
json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java

@ -45,9 +45,13 @@ public class PathCompiler {
private final LinkedList<Predicate> filterStack;
private final CharacterIndex path;
private PathCompiler(String path, LinkedList<Predicate> filterStack) {
private PathCompiler(String path, LinkedList<Predicate> filterStack){
this(new CharacterIndex(path), filterStack);
}
private PathCompiler(CharacterIndex path, LinkedList<Predicate> filterStack){
this.filterStack = filterStack;
this.path = new CharacterIndex(path);
this.path = path;
}
private Path compile() {
@ -57,16 +61,18 @@ public class PathCompiler {
public static Path compile(String path, final Predicate... filters) {
try {
path = path.trim();
CharacterIndex ci = new CharacterIndex(path);
ci.trim();
if(!(path.charAt(0) == DOC_CONTEXT) && !(path.charAt(0) == EVAL_CONTEXT)){
path = "$." + path;
if(!( ci.charAt(0) == DOC_CONTEXT) && !( ci.charAt(0) == EVAL_CONTEXT)){
ci = new CharacterIndex("$." + path);
ci.trim();
}
if(path.endsWith(".")){
if(ci.lastCharIs('.')){
fail("Path must not end with a '.' or '..'");
}
LinkedList filterStack = new LinkedList<Predicate>(asList(filters));
Path p = new PathCompiler(path.trim(), filterStack).compile();
LinkedList<Predicate> filterStack = new LinkedList<Predicate>(asList(filters));
Path p = new PathCompiler(ci, filterStack).compile();
return p;
} catch (Exception e) {
InvalidPathException ipe;
@ -103,7 +109,6 @@ public class PathCompiler {
}
RootPathToken pathToken = PathTokenFactory.createRootPathToken(path.currentChar());
PathTokenAppender appender = pathToken.getPathTokenAppender();
if (path.currentIsTail()) {
return pathToken;
@ -115,6 +120,7 @@ public class PathCompiler {
fail("Illegal character at position " + path.position() + " expected '.' or '[");
}
PathTokenAppender appender = pathToken.getPathTokenAppender();
readNextToken(appender);
return pathToken;
@ -261,7 +267,6 @@ public class PathCompiler {
* an array.
*/
private List<Parameter> parseFunctionParameters(String funcName) {
PathToken currentToken;
ParamType type = null;
// Parenthesis starts at 1 since we're marking the start of a function call, the close paren will denote the

3
json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java

@ -125,8 +125,9 @@ public abstract class PathToken {
protected void handleArrayIndex(int index, String currentPath, Object model, EvaluationContextImpl ctx) {
String evalPath = Utils.concat(currentPath, "[", String.valueOf(index), "]");
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, index) : PathRef.NO_OP;
int effectiveIndex = index < 0 ? ctx.jsonProvider().length(model) + index : index;
try {
Object evalHit = ctx.jsonProvider().getArrayIndex(model, index);
Object evalHit = ctx.jsonProvider().getArrayIndex(model, effectiveIndex);
if (isLeaf()) {
ctx.addResult(evalPath, pathRef, evalHit);
} else {

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;
}

8
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
@ -96,8 +96,8 @@ public class JsonOrgJsonProvider extends AbstractJsonProvider {
public Object getMapValue(Object obj, String key) {
try {
JSONObject jsonObject = toJsonObject(obj);
Object o = jsonObject.get(key);
if (!jsonObject.has(key)) {
Object o = jsonObject.opt(key);
if (o == null) {
return UNDEFINED;
} else {
return unwrap(o);

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

@ -89,6 +89,13 @@ public class FilterTest extends BaseTest {
assertThat(filter(where("string-key").eq(null)).apply(createPredicateContext(json))).isEqualTo(false);
}
@Test
public void arr_eq_evals() {
assertThat(filter(where("arr-empty").eq("[]")).apply(createPredicateContext(json))).isEqualTo(true);
assertThat(filter(where("int-arr").eq("[0,1,2,3,4]")).apply(createPredicateContext(json))).isEqualTo(true);
assertThat(filter(where("int-arr").eq("[0,1,2,3]")).apply(createPredicateContext(json))).isEqualTo(false);
assertThat(filter(where("int-arr").eq("[0,1,2,3,4,5]")).apply(createPredicateContext(json))).isEqualTo(false);
}
//----------------------------------------------------------------------------
//
// NE

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);

8
json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java

@ -44,4 +44,12 @@ public class JsonOrgJsonProviderTest extends BaseTest {
assertThat(books.size()).isEqualTo(4);
}
@Test
public void read_books_with_isbn() {
JSONArray books = using(JSON_ORG_CONFIGURATION).parse(JSON_DOCUMENT).read("$..book[?(@.isbn)]");
assertThat(books.length()).isEqualTo(2);
}
}

128
json-path/src/test/java/com/jayway/jsonpath/ProviderInTest.java

@ -0,0 +1,128 @@
package com.jayway.jsonpath;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.gson.JsonArray;
import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonOrgJsonProvider;
import com.jayway.jsonpath.spi.json.JsonSmartJsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import com.jayway.jsonpath.spi.mapper.JsonOrgMappingProvider;
import com.jayway.jsonpath.spi.mapper.JsonSmartMappingProvider;
import org.assertj.core.util.Lists;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class ProviderInTest {
private final String JSON = "[{\"foo\": \"bar\"}, {\"foo\": \"baz\"}]";
private final String EQUALS_FILTER = "$.[?(@.foo == %s)].foo";
private final String IN_FILTER = "$.[?(@.foo in [%s])].foo";
private final String DOUBLE_QUOTES = "\"bar\"";
private final String DOUBLE_QUOTES_EQUALS_FILTER = String.format(EQUALS_FILTER, DOUBLE_QUOTES);
private final String DOUBLE_QUOTES_IN_FILTER = String.format(IN_FILTER, DOUBLE_QUOTES);
private final String SINGLE_QUOTES = "'bar'";
private final String SINGLE_QUOTES_EQUALS_FILTER = String.format(EQUALS_FILTER, SINGLE_QUOTES);
private final String SINGLE_QUOTES_IN_FILTER = String.format(IN_FILTER, SINGLE_QUOTES);
@Rule
public final ExpectedException exception = ExpectedException.none();
@Test
public void testJsonPathQuotesJackson() throws Exception {
final Configuration jackson = Configuration.builder().jsonProvider(new JacksonJsonProvider()).mappingProvider(new JacksonMappingProvider()).build();
final DocumentContext ctx = JsonPath.using(jackson).parse(JSON);
final List<String> doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
assertEquals(Lists.newArrayList("bar"), doubleQuoteEqualsResult);
final List<String> singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
assertEquals(doubleQuoteEqualsResult, singleQuoteEqualsResult);
final List<String> doubleQuoteInResult = ctx.read(DOUBLE_QUOTES_IN_FILTER);
assertEquals(doubleQuoteInResult, doubleQuoteEqualsResult);
exception.expect(InvalidJsonException.class);
ctx.read(SINGLE_QUOTES_IN_FILTER);
}
@Test
public void testJsonPathQuotesJacksonJsonNode() throws Exception {
final Configuration jacksonJsonNode = Configuration.builder().jsonProvider(new JacksonJsonNodeJsonProvider()).mappingProvider(new JacksonMappingProvider()).build();
final DocumentContext ctx = JsonPath.using(jacksonJsonNode).parse(JSON);
final ArrayNode doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
assertEquals("bar", doubleQuoteEqualsResult.get(0).asText());
final ArrayNode singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
assertEquals(doubleQuoteEqualsResult, singleQuoteEqualsResult);
final ArrayNode doubleQuoteInResult = ctx.read(DOUBLE_QUOTES_IN_FILTER);
assertEquals(doubleQuoteInResult, doubleQuoteEqualsResult);
exception.expect(InvalidJsonException.class);
ctx.read(SINGLE_QUOTES_IN_FILTER);
}
@Test
public void testJsonPathQuotesGson() throws Exception {
final Configuration gson = Configuration.builder().jsonProvider(new GsonJsonProvider()).mappingProvider(new GsonMappingProvider()).build();
final DocumentContext ctx = JsonPath.using(gson).parse(JSON);
final JsonArray doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
assertEquals("bar", doubleQuoteEqualsResult.get(0).getAsString());
final JsonArray singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
assertEquals(doubleQuoteEqualsResult, singleQuoteEqualsResult);
final JsonArray doubleQuoteInResult = ctx.read(DOUBLE_QUOTES_IN_FILTER);
assertEquals(doubleQuoteInResult, doubleQuoteEqualsResult);
final JsonArray singleQuoteInResult = ctx.read(SINGLE_QUOTES_IN_FILTER);
assertEquals(doubleQuoteInResult, singleQuoteInResult);
}
@Test
public void testJsonPathQuotesJsonOrg() throws Exception {
final Configuration jsonOrg = Configuration.builder().jsonProvider(new JsonOrgJsonProvider()).mappingProvider(new JsonOrgMappingProvider()).build();
final DocumentContext ctx = JsonPath.using(jsonOrg).parse(JSON);
final org.json.JSONArray doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
assertEquals("bar", doubleQuoteEqualsResult.get(0));
final org.json.JSONArray singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
assertEquals(doubleQuoteEqualsResult.get(0), singleQuoteEqualsResult.get(0));
final org.json.JSONArray doubleQuoteInResult = ctx.read(DOUBLE_QUOTES_IN_FILTER);
assertEquals(doubleQuoteInResult.get(0), doubleQuoteEqualsResult.get(0));
final org.json.JSONArray singleQuoteInResult = ctx.read(SINGLE_QUOTES_IN_FILTER);
assertEquals(doubleQuoteInResult.get(0), singleQuoteInResult.get(0));
}
@Test
public void testJsonPathQuotesJsonSmart() throws Exception {
final Configuration jsonSmart = Configuration.builder().jsonProvider(new JsonSmartJsonProvider()).mappingProvider(new JsonSmartMappingProvider()).build();
final DocumentContext ctx = JsonPath.using(jsonSmart).parse(JSON);
final net.minidev.json.JSONArray doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
assertEquals("bar", doubleQuoteEqualsResult.get(0));
final net.minidev.json.JSONArray singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
assertEquals(doubleQuoteEqualsResult, singleQuoteEqualsResult);
final net.minidev.json.JSONArray doubleQuoteInResult = ctx.read(DOUBLE_QUOTES_IN_FILTER);
assertEquals(doubleQuoteInResult, doubleQuoteEqualsResult);
final net.minidev.json.JSONArray singleQuoteInResult = ctx.read(SINGLE_QUOTES_IN_FILTER);
assertEquals(doubleQuoteInResult, singleQuoteInResult);
}
}

20
json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java

@ -999,4 +999,24 @@ public class IssuesTest extends BaseTest {
assertThat(objectNode.get("can delete").isNull());
assertThat(objectNode.get("can't delete").isNull());
}
@Test
public void issue_309(){
String json = "{\n" +
"\"jsonArr\": [\n" +
" {\n" +
" \"name\":\"nOne\"\n" +
" },\n" +
" {\n" +
" \"name\":\"nTwo\"\n" +
" }\n" +
" ]\n" +
"}";
DocumentContext doc = JsonPath.parse(json).set("$.jsonArr[1].name", "Jayway");
assertThat(doc.read("$.jsonArr[0].name")).isEqualTo("nOne");
assertThat(doc.read("$.jsonArr[1].name")).isEqualTo("Jayway");
}
}

9
json-path/src/test/java/com/jayway/jsonpath/old/internal/ArrayIndexFilterTest.java

@ -1,6 +1,9 @@
package com.jayway.jsonpath.old.internal;
import com.jayway.jsonpath.JsonPath;
import org.junit.Assert;
import org.hamcrest.Matchers;
import org.junit.Test;
@ -43,4 +46,10 @@ public class ArrayIndexFilterTest {
assertThat(result, Matchers.contains(1, 3, 5));
}
@Test
public void can_access_items_from_end_with_negative_index() {
int result = JsonPath.parse(JSON).read("$[-3]");
Assert.assertEquals(8, result);
}
}

Loading…
Cancel
Save