Browse Source

removed braces added support for JSON - need to parse JSON now...

pull/167/head
Matthew J Greenwood 9 years ago
parent
commit
fadc946032
  1. 9
      json-path/src/main/java/com/jayway/jsonpath/internal/function/ParamType.java
  2. 17
      json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java
  3. 126
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
  4. 4
      json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java

9
json-path/src/main/java/com/jayway/jsonpath/internal/function/ParamType.java

@ -0,0 +1,9 @@
package com.jayway.jsonpath.internal.function;
/**
* Created by mgreenwood on 12/11/15.
*/
public enum ParamType {
JSON,
PATH
}

17
json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java

@ -6,10 +6,13 @@ import com.jayway.jsonpath.internal.Path;
* Created by matt@mjgreenwood.net on 12/10/15.
*/
public class Parameter {
private final Path path;
private ParamType type;
private Path path;
private Object cachedValue;
private Boolean evaluated = false;
public Parameter() {}
public Parameter(Path path) {
this.path = path;
}
@ -33,4 +36,16 @@ public class Parameter {
public boolean hasEvaluated() {
return evaluated;
}
public ParamType getType() {
return type;
}
public void setType(ParamType type) {
this.type = type;
}
public void setPath(Path path) {
this.path = path;
}
}

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

@ -5,6 +5,7 @@ import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.internal.CharacterIndex;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.filter.FilterCompiler;
import com.jayway.jsonpath.internal.function.ParamType;
import com.jayway.jsonpath.internal.function.Parameter;
import java.util.ArrayList;
@ -80,19 +81,23 @@ public class PathCompiler {
private void readWhitespace() {
while (path.inBounds()) {
char c = path.currentChar();
if (c != SPACE && c != TAB && c != LF && c != CR) {
if (!isWhitespace(c)) {
break;
}
path.incrementPosition(1);
}
}
private Boolean isPathContext(char c) {
return (c == DOC_CONTEXT || c == EVAL_CONTEXT);
}
//[$ | @]
private RootPathToken readContextToken() {
readWhitespace();
if (!path.currentCharIs(DOC_CONTEXT) && !path.currentCharIs(EVAL_CONTEXT)) {
if (!isPathContext(path.currentChar())) {
throw new InvalidPathException("Path must start with '$' or '@'");
}
@ -199,7 +204,7 @@ public class PathCompiler {
// read the next token to determine if we have a simple no-args function call
char c = path.charAt(readPosition + 1);
if (c != CLOSE_PARENTHESIS) {
// parse the arguments of the function - arguments that are inner queries will be single quoted parameters
// parse the arguments of the function - arguments that are inner queries or JSON document(s)
functionParameters = parseFunctionParameters(readPosition);
} else {
path.setPosition(readPosition + 1);
@ -230,6 +235,23 @@ public class PathCompiler {
* set if the parameter is an expression. If the parameter is a JSON document then the value of the cachedValue is
* set on the object.
*
* Sequence for parsing out the parameters:
*
* This code has its own tokenizer - it does some rudimentary level of lexing in that it can distinguish between JSON block parameters
* and sub-JSON blocks - it effectively regex's out the parameters into string blocks that can then be passed along to the appropriate parser.
* Since sub-jsonpath expressions can themselves contain other function calls this routine needs to be sensitive to token counting to
* determine the boundaries. Since the Path parser isn't aware of JSON processing this uber routine is needed.
*
* Parameters are separated by COMMAs ','
*
* <pre>
* doc = {"numbers": [1,2,3,4,5,6,7,8,9,10]}
*
* $.sum({10}, $.numbers.avg())
* </pre>
*
* The above is a valid function call, we're first summing 10 + avg of 1...10 (5.5) so the total should be 15.5
*
* @param readPosition
* The current position within the stream we've advanced - TODO remove the need for this...
* @return
@ -239,59 +261,93 @@ public class PathCompiler {
*/
private List<Parameter> parseFunctionParameters(int readPosition) {
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
// last parameter boundary
Integer groupParen = 1, groupBracket = 0, groupBrace = 0;
Boolean endOfStream = false;
List<Parameter> parameters = new ArrayList<Parameter>();
StringBuffer parameter = new StringBuffer();
Boolean insideParameter = false;
int braceCount = 0, parenCount = 1;
while (path.inBounds(readPosition)) {
while (path.inBounds(readPosition) && !endOfStream) {
char c = path.charAt(readPosition++);
// we're at the start of the stream, and don't know what type of parameter we have
if (type == null) {
if (isWhitespace(c)) {
continue;
}
if (c == OPEN_BRACE) {
braceCount++;
type = ParamType.JSON;
}
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);
Path path = compiler.compile();
parameters.add(new Parameter(path));
parameter.delete(0, parameter.length());
else if (isPathContext(c)) {
type = ParamType.PATH; // read until we reach a terminating comma and we've reset grouping to zero
}
}
else if (c == COMMA && braceCount == 0) {
parameter.delete(0, parameter.length());
switch (c) {
case OPEN_PARENTHESIS:
groupParen++;
break;
case OPEN_BRACE:
groupBrace++;
break;
case OPEN_SQUARE_BRACKET:
groupBracket++;
break;
case CLOSE_BRACE:
groupBrace--;
break;
case CLOSE_SQUARE_BRACKET:
groupBracket--;
break;
// In either the close paren case where we have zero paren groups left, capture the parameter, or where
// we've encountered a COMMA do the same
case CLOSE_PARENTHESIS:
groupParen--;
if (0 != groupParen) {
parameter.append(c);
}
else {
if (c == CLOSE_PARENTHESIS) {
parenCount--;
if (parenCount == 0) {
if (parameter.length() > 0) {
// inner parse the parameter expression to pass along to the function
case COMMA:
// In this state we've reach the end of a function parameter and we can pass along the parameter string
// to the parser
if (null != type && (0 == groupBrace && 0 == groupBracket && ((0 == groupParen && CLOSE_PARENTHESIS == c) || 1 == groupParen))) {
Parameter param = new Parameter();
param.setType(type);
if (type == ParamType.JSON) {
// parse the json and set the value
param.setCachedValue(parameter.toString());
param.setEvaluated(true);
}
else if (type == ParamType.PATH) {
LinkedList<Predicate> predicates = new LinkedList<Predicate>();
PathCompiler compiler = new PathCompiler(parameter.toString(), predicates);
parameters.add(new Parameter(compiler.compile()));
}
break;
}
else {
parameter.append(c);
param.setPath(compiler.compile());
}
parameters.add(param);
parameter.delete(0, parameter.length());
type = null;
endOfStream = (0 == groupParen);
}
else if (c == OPEN_PARENTHESIS) {
parenCount++;
parameter.append(c);
break;
}
else {
if (type != null && !(c == COMMA && 0 == groupBrace && 0 == groupBracket && 1 == groupParen)) {
parameter.append(c);
}
}
}
path.setPosition(readPosition);
return parameters;
}
private boolean isWhitespace(char c) {
return (c == SPACE || c == TAB || c == LF || c == CR);
}
//
// [?], [?,?, ..]
//

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

@ -29,7 +29,7 @@ public class NestedFunctionTest extends BaseFunctionTest {
@Test
public void testParameterAverageFunctionCall() {
verifyMathFunction(conf, "$.avg({$.numbers.min()}, {$.numbers.max()})", 5.5);
verifyMathFunction(conf, "$.avg($.numbers.min(), $.numbers.max())", 5.5);
}
@Test
@ -47,6 +47,6 @@ public class NestedFunctionTest extends BaseFunctionTest {
*/
@Test
public void testArrayAverageFunctionCallWithParameters() {
verifyMathFunction(conf, "$.numbers.sum({$.numbers.min()}, {$.numbers.max()})", 66.0);
verifyMathFunction(conf, "$.numbers.sum($.numbers.min(), $.numbers.max())", 66.0);
}
}

Loading…
Cancel
Save