Browse Source

Initiial implementation of PathCompiler changes to support functions that take other JsonPath elements - need to decide whether path parameters are relative to the function location (probably not)

Example:

$.sum({$.numbers.min()}, {$.numbers.max()})

You could also do something such as:

$.numbers.add({$.numbers.min()})

where add for each element in the array took another JsonPath parameter that we'd add to resulting in an array result with the min value of numbers added to each element in the number's array.

Obviously there's better examples than the above - but these changes allow the PathCompiler to parse the function parameters for nested JsonPath's
pull/167/head
Matthew J Greenwood 9 years ago
parent
commit
76cfa0e30f
  1. 12
      json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java
  2. 74
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
  3. 5
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java
  4. 34
      json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java
  5. 2
      json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java

12
json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java

@ -1,9 +1,12 @@
package com.jayway.jsonpath.internal.path;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.function.PathFunction;
import com.jayway.jsonpath.internal.function.PathFunctionFactory;
import java.util.List;
/**
* Token representing a Function call to one of the functions produced via the FunctionFactory
*
@ -15,13 +18,16 @@ public class FunctionPathToken extends PathToken {
private final String functionName;
private final String pathFragment;
private final List<Path> functionParams;
public FunctionPathToken(String pathFragment) {
public FunctionPathToken(String pathFragment, List<Path> parameters) {
this.pathFragment = pathFragment;
if(pathFragment.endsWith("()")){
functionName = pathFragment.substring(0, pathFragment.length()-2);
if(null != pathFragment){
functionName = pathFragment;
functionParams = parameters;
} else {
functionName = null;
functionParams = null;
}
}

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

@ -22,6 +22,9 @@ public class PathCompiler {
private static final char OPEN_SQUARE_BRACKET = '[';
private static final char CLOSE_SQUARE_BRACKET = ']';
private static final char OPEN_PARENTHESIS = '(';
private static final char CLOSE_PARENTHESIS = ')';
private static final char OPEN_BRACE = '{';
private static final char CLOSE_BRACE = '}';
private static final char WILDCARD = '*';
private static final char PERIOD = '.';
@ -151,26 +154,48 @@ public class PathCompiler {
int readPosition = startPosition;
int endPosition = 0;
boolean isFunction = false;
while (path.inBounds(readPosition)) {
char c = path.charAt(readPosition);
if (c == SPACE) {
throw new InvalidPathException("Use bracket notion ['my prop'] if your property contains blank characters. position: " + path.position());
}
if (c == PERIOD || c == OPEN_SQUARE_BRACKET) {
else if (c == PERIOD || c == OPEN_SQUARE_BRACKET) {
endPosition = readPosition;
break;
}
else if (c == OPEN_PARENTHESIS) {
isFunction = true;
endPosition = readPosition++;
break;
}
readPosition++;
}
if (endPosition == 0) {
endPosition = path.length();
}
List<Path> functionParameters = null;
if (isFunction) {
// read the next token to determine if we have a simple no-args function call
char c = path.charAt(readPosition++);
if (c != CLOSE_PARENTHESIS) {
// parse the arguments of the function - arguments that are inner queries will be single quoted parameters
functionParameters = parseFunctionParameters(readPosition);
}
else {
path.setPosition(readPosition);
}
}
else {
path.setPosition(endPosition);
}
String property = path.subSequence(startPosition, endPosition).toString();
if(property.endsWith("()")){
appender.appendPathToken(PathTokenFactory.createFunctionPathToken(property));
if(isFunction){
appender.appendPathToken(PathTokenFactory.createFunctionPathToken(property, functionParameters));
} else {
appender.appendPathToken(PathTokenFactory.createSinglePropertyPathToken(property, SINGLE_QUOTE));
}
@ -178,6 +203,49 @@ public class PathCompiler {
return path.currentIsTail() || readNextToken(appender);
}
private List<Path> parseFunctionParameters(int readPosition) {
PathToken currentToken;
List<Path> parameters = new ArrayList<Path>();
StringBuffer parameter = new StringBuffer();
Boolean insideParameter = false;
int braceCount = 0, parenCount = 1;
while (path.inBounds(readPosition)) {
char c = path.charAt(readPosition);
if (c == OPEN_BRACE) {
braceCount++;
}
else if (c == CLOSE_BRACE) {
braceCount--;
if (0 == braceCount && parameter.length() > 0) {
// inner parse the parameter expression to pass along to the function
LinkedList<Predicate> predicates = new LinkedList<Predicate>();
PathCompiler compiler = new PathCompiler(parameter.toString(), predicates);
parameters.add(compiler.compile());
}
}
else if (c == COMMA && braceCount == 0) {
parameter.delete(0, parameter.length());
}
else {
parameter.append(c);
if (c == CLOSE_PARENTHESIS) {
parenCount--;
if (parenCount == 0) {
break;
}
}
else if (c == OPEN_PARENTHESIS) {
parenCount++;
}
}
readPosition++;
}
path.setPosition(readPosition);
return parameters;
}
//
// [?], [?,?, ..]
//

5
json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java

@ -1,6 +1,7 @@
package com.jayway.jsonpath.internal.path;
import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.internal.Path;
import java.util.Collection;
import java.util.List;
@ -45,7 +46,7 @@ public class PathTokenFactory {
return new PredicatePathToken(predicate);
}
public static PathToken createFunctionPathToken(String function) {
return new FunctionPathToken((function));
public static PathToken createFunctionPathToken(String function, List<Path> parameters) {
return new FunctionPathToken(function, parameters);
}
}

34
json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java

@ -0,0 +1,34 @@
package com.jayway.jsonpath.internal.function;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Configurations;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by matt@mjgreenwood.net on 12/10/15.
*/
@RunWith(Parameterized.class)
public class NestedFunctionTest extends BaseFunctionTest {
private static final Logger logger = LoggerFactory.getLogger(NumericPathFunctionTest.class);
private Configuration conf = Configurations.GSON_CONFIGURATION;
public NestedFunctionTest(Configuration conf) {
logger.debug("Testing with configuration {}", conf.getClass().getName());
this.conf = conf;
}
@Parameterized.Parameters
public static Iterable<Configuration> configurations() {
return Configurations.configurations();
}
@Test
public void testAverageOfDoubles() {
verifyMathFunction(conf, "$.sum({$.numbers.min()}, {$.numbers.max()})", 5.5);
}
}

2
json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java

@ -57,7 +57,7 @@ public class NumericPathFunctionTest extends BaseFunctionTest {
@Test
public void testMinOfDouble() {
verifyMathFunction(conf, "$.numbers.min()", 1d);
verifyMathFunction(conf, "$.numbers.min(foobar)", 1d);
}
@Test

Loading…
Cancel
Save