Browse Source

Merge pull request #377 from mgreenwood1001/Issue191

Issue #191 fixes both $..allTheThings.max() and $.max($..allTheThings)
pull/378/merge
kallestenflo 7 years ago committed by GitHub
parent
commit
b4c26b25fa
  1. 67
      json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java
  2. 1
      json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
  3. 7
      json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java
  4. 8
      json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java
  5. 48
      json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java
  6. 5
      json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java
  7. 3
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
  8. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java
  9. 67
      json-path/src/test/java/com/jayway/jsonpath/internal/function/Issue191.java
  10. 1258
      json-path/src/test/resources/issue_191.json

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

@ -1,11 +1,15 @@
package com.jayway.jsonpath.internal.function;
import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.function.latebinding.ILateBindingValue;
import com.jayway.jsonpath.internal.function.latebinding.PathLateBindingValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Created by matt@mjgreenwood.net on 12/10/15.
* Defines a parameter as passed to a function with late binding support for lazy evaluation.
*/
public class Parameter {
private ParamType type;
@ -65,4 +69,63 @@ public class Parameter {
public void setJson(String json) {
this.json = json;
}
/**
* Translate the collection of parameters into a collection of values of type T.
*
* @param type
* The type to translate the collection into.
*
* @param ctx
* Context.
*
* @param parameters
* Collection of parameters.
*
* @param <T>
* Type T returned as a List of T.
*
* @return
* List of T either empty or containing contents.
*/
public static <T> List<T> toList(final Class<T> type, final EvaluationContext ctx, final List<Parameter> parameters) {
List<T> values = new ArrayList();
if (null != parameters) {
for (Parameter param : parameters) {
consume(type, ctx, values, param.getValue());
}
}
return values;
}
/**
* Either consume the object as an array and add each element to the collection, or alternatively add each element
*
* @param expectedType
* the expected class type to consume, if null or not of this type the element is not added to the array.
*
* @param ctx
* the JSON context to determine if this is an array or value.
*
* @param collection
* The collection to append into.
*
* @param value
* The value to evaluate.
*/
public static void consume(Class expectedType, EvaluationContext ctx, Collection collection, Object value) {
if (ctx.configuration().jsonProvider().isArray(value)) {
for (Object o : ctx.configuration().jsonProvider().toIterable(value)) {
if (o != null && expectedType.isAssignableFrom(o.getClass())) {
collection.add(o);
} else if (o != null && expectedType == String.class) {
collection.add(o.toString());
}
}
} else {
if (value != null && expectedType.isAssignableFrom(value.getClass())) {
collection.add(value);
}
}
}
}

1
json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java

@ -21,7 +21,6 @@ import java.util.Map;
* Leverages the function's name in order to determine which function to execute which is maintained internally
* here via a static map
*
* Created by mattg on 6/27/15.
*/
public class PathFunctionFactory {

7
json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java

@ -48,12 +48,9 @@ public abstract class AbstractAggregation implements PathFunction {
}
}
if (parameters != null) {
for (Parameter param : parameters) {
Object value = param.getValue();
if (null != value && value instanceof Number) {
for (Number value : Parameter.toList(Number.class, ctx, parameters)) {
count++;
next((Number)value);
}
next(value);
}
}
if (count != 0) {

8
json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java

@ -11,7 +11,6 @@ import java.util.List;
* String function concat - simple takes a list of arguments and/or an array and concatenates them together to form a
* single string
*
* Created by mgreenwood on 12/11/15.
*/
public class Concatenate implements PathFunction {
@Override
@ -26,11 +25,8 @@ public class Concatenate implements PathFunction {
}
}
if (parameters != null) {
for (Parameter param : parameters) {
Object value = param.getValue();
if (value != null) {
result.append(value.toString());
}
for (String value : Parameter.toList(String.class, ctx, parameters)) {
result.append(value);
}
}
return result.toString();

48
json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java

@ -19,9 +19,13 @@ import com.jayway.jsonpath.internal.EvaluationAbortException;
import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.function.ParamType;
import com.jayway.jsonpath.internal.function.Parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
public class CompiledPath implements Path {
private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class);
@ -32,7 +36,7 @@ public class CompiledPath implements Path {
public CompiledPath(RootPathToken root, boolean isRootPath) {
this.root = root;
this.root = invertScannerFunctionRelationship(root);
this.isRootPath = isRootPath;
}
@ -41,6 +45,48 @@ public class CompiledPath implements Path {
return isRootPath;
}
/**
* In the event the writer of the path referenced a function at the tail end of a scanner, augment the query such
* that the root node is the function and the parameter to the function is the scanner. This way we maintain
* relative sanity in the path expression, functions either evaluate scalar values or arrays, they're
* not re-entrant nor should they maintain state, they do however take parameters.
*
* @param path
* this is our old root path which will become a parameter (assuming there's a scanner terminated by a function
*
* @return
* A function with the scanner as input, or if this situation doesn't exist just the input path
*/
private RootPathToken invertScannerFunctionRelationship(final RootPathToken path) {
if (path.isFunctionPath() && path.next() instanceof ScanPathToken) {
PathToken token = path;
PathToken prior = null;
while (null != (token = token.next()) && !(token instanceof FunctionPathToken)) {
prior = token;
}
// Invert the relationship $..path.function() to $.function($..path)
if (token instanceof FunctionPathToken) {
prior.setNext(null);
path.setTail(prior);
// Now generate a new parameter from our path
Parameter parameter = new Parameter();
parameter.setPath(new CompiledPath(path, true));
parameter.setType(ParamType.PATH);
((FunctionPathToken)token).setParameters(Arrays.asList(parameter));
RootPathToken functionRoot = new RootPathToken('$');
functionRoot.setTail(token);
functionRoot.setNext(token);
// Define the function as the root
return functionRoot;
}
}
return path;
}
@Override
public EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration, boolean forUpdate) {
if (logger.isDebugEnabled()) {

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

@ -20,7 +20,7 @@ public class FunctionPathToken extends PathToken {
private final String functionName;
private final String pathFragment;
private final List<Parameter> functionParams;
private List<Parameter> functionParams;
public FunctionPathToken(String pathFragment, List<Parameter> parameters) {
this.pathFragment = pathFragment + ((parameters != null && parameters.size() > 0) ? "(...)" : "()");
@ -81,4 +81,7 @@ public class FunctionPathToken extends PathToken {
}
public void setParameters(List<Parameter> parameters) {
this.functionParams = parameters;
}
}

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

@ -215,4 +215,7 @@ public abstract class PathToken {
protected abstract String getPathFragment();
public void setNext(final PathToken next) {
this.next = next;
}
}

4
json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java

@ -76,4 +76,8 @@ public class RootPathToken extends PathToken {
public boolean isFunctionPath() {
return (tail instanceof FunctionPathToken);
}
public void setTail(PathToken token) {
this.tail = token;
}
}

67
json-path/src/test/java/com/jayway/jsonpath/internal/function/Issue191.java

@ -0,0 +1,67 @@
package com.jayway.jsonpath.internal.function;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Configurations;
import com.jayway.jsonpath.JsonPath;
import org.junit.Test;
import java.io.InputStream;
import static org.junit.Assert.assertEquals;
/**
* TDD for Issue 191
*
* Shows aggregation across fields rather than within a single entity.
*
*/
public class Issue191 {
private Configuration conf = Configurations.GSON_CONFIGURATION;
@Test
public void testResultSetNumericComputation() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
Long value = JsonPath.parse(stream).read("$.sum($..timestamp)", Long.class);
assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set",
Long.valueOf(35679716813L), value);
}
@Test
public void testResultSetNumericComputationTail() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
Long value = JsonPath.parse(stream).read("$..timestamp.sum()", Long.class);
assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set",
Long.valueOf(35679716813L), value);
}
@Test
public void testResultSetNumericComputationRecursiveReplacement() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
Long value = JsonPath.parse(stream).read("$.max($..timestamp.avg(), $..timestamp.stddev())", Long.class);
assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set",
Long.valueOf(1427188672L), value);
}
@Test
public void testMultipleResultSetSums() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
Long value = JsonPath.parse(stream).read("$.sum($..timestamp, $..cpus)", Long.class);
assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set",
Long.valueOf(35679716835L), value);
}
@Test
public void testConcatResultSet() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
String concatResult = JsonPath.parse(stream).read("$.concat($..state)", String.class);
assertEquals("Expected a string length to be a concat of all of the states", concatResult.length(), 806);
}
@Test
public void testConcatWithNumericValueAsString() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
String concatResult = JsonPath.parse(stream).read("$.concat($..cpus)", String.class);
assertEquals("Expected a string length to be a concat of all of the cpus", concatResult.length(), 489);
}
}

1258
json-path/src/test/resources/issue_191.json

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save