Skip to content

Commit b4c26b2

Browse files
authored
Merge pull request #377 from mgreenwood1001/Issue191
Issue #191 fixes both $..allTheThings.max() and $.max($..allTheThings)
2 parents efdab97 + 02c88d7 commit b4c26b2

File tree

10 files changed

+1453
-17
lines changed

10 files changed

+1453
-17
lines changed

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

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package com.jayway.jsonpath.internal.function;
22

3+
import com.jayway.jsonpath.internal.EvaluationContext;
34
import com.jayway.jsonpath.internal.Path;
45
import com.jayway.jsonpath.internal.function.latebinding.ILateBindingValue;
5-
import com.jayway.jsonpath.internal.function.latebinding.PathLateBindingValue;
6+
7+
import java.util.ArrayList;
8+
import java.util.Collection;
9+
import java.util.List;
610

711
/**
8-
* Created by matt@mjgreenwood.net on 12/10/15.
12+
* Defines a parameter as passed to a function with late binding support for lazy evaluation.
913
*/
1014
public class Parameter {
1115
private ParamType type;
@@ -65,4 +69,63 @@ public String getJson() {
6569
public void setJson(String json) {
6670
this.json = json;
6771
}
72+
73+
/**
74+
* Translate the collection of parameters into a collection of values of type T.
75+
*
76+
* @param type
77+
* The type to translate the collection into.
78+
*
79+
* @param ctx
80+
* Context.
81+
*
82+
* @param parameters
83+
* Collection of parameters.
84+
*
85+
* @param <T>
86+
* Type T returned as a List of T.
87+
*
88+
* @return
89+
* List of T either empty or containing contents.
90+
*/
91+
public static <T> List<T> toList(final Class<T> type, final EvaluationContext ctx, final List<Parameter> parameters) {
92+
List<T> values = new ArrayList();
93+
if (null != parameters) {
94+
for (Parameter param : parameters) {
95+
consume(type, ctx, values, param.getValue());
96+
}
97+
}
98+
return values;
99+
}
100+
101+
/**
102+
* Either consume the object as an array and add each element to the collection, or alternatively add each element
103+
*
104+
* @param expectedType
105+
* the expected class type to consume, if null or not of this type the element is not added to the array.
106+
*
107+
* @param ctx
108+
* the JSON context to determine if this is an array or value.
109+
*
110+
* @param collection
111+
* The collection to append into.
112+
*
113+
* @param value
114+
* The value to evaluate.
115+
*/
116+
public static void consume(Class expectedType, EvaluationContext ctx, Collection collection, Object value) {
117+
if (ctx.configuration().jsonProvider().isArray(value)) {
118+
for (Object o : ctx.configuration().jsonProvider().toIterable(value)) {
119+
if (o != null && expectedType.isAssignableFrom(o.getClass())) {
120+
collection.add(o);
121+
} else if (o != null && expectedType == String.class) {
122+
collection.add(o.toString());
123+
}
124+
}
125+
} else {
126+
if (value != null && expectedType.isAssignableFrom(value.getClass())) {
127+
collection.add(value);
128+
}
129+
}
130+
}
68131
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
* Leverages the function's name in order to determine which function to execute which is maintained internally
2222
* here via a static map
2323
*
24-
* Created by mattg on 6/27/15.
2524
*/
2625
public class PathFunctionFactory {
2726

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,9 @@ public Object invoke(String currentPath, PathRef parent, Object model, Evaluatio
4848
}
4949
}
5050
if (parameters != null) {
51-
for (Parameter param : parameters) {
52-
Object value = param.getValue();
53-
if (null != value && value instanceof Number) {
54-
count++;
55-
next((Number)value);
56-
}
51+
for (Number value : Parameter.toList(Number.class, ctx, parameters)) {
52+
count++;
53+
next(value);
5754
}
5855
}
5956
if (count != 0) {

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
* String function concat - simple takes a list of arguments and/or an array and concatenates them together to form a
1212
* single string
1313
*
14-
* Created by mgreenwood on 12/11/15.
1514
*/
1615
public class Concatenate implements PathFunction {
1716
@Override
@@ -26,11 +25,8 @@ public Object invoke(String currentPath, PathRef parent, Object model, Evaluatio
2625
}
2726
}
2827
if (parameters != null) {
29-
for (Parameter param : parameters) {
30-
Object value = param.getValue();
31-
if (value != null) {
32-
result.append(value.toString());
33-
}
28+
for (String value : Parameter.toList(String.class, ctx, parameters)) {
29+
result.append(value);
3430
}
3531
}
3632
return result.toString();

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

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@
1919
import com.jayway.jsonpath.internal.EvaluationContext;
2020
import com.jayway.jsonpath.internal.Path;
2121
import com.jayway.jsonpath.internal.PathRef;
22+
import com.jayway.jsonpath.internal.function.ParamType;
23+
import com.jayway.jsonpath.internal.function.Parameter;
2224
import org.slf4j.Logger;
2325
import org.slf4j.LoggerFactory;
2426

27+
import java.util.Arrays;
28+
2529
public class CompiledPath implements Path {
2630

2731
private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class);
@@ -32,7 +36,7 @@ public class CompiledPath implements Path {
3236

3337

3438
public CompiledPath(RootPathToken root, boolean isRootPath) {
35-
this.root = root;
39+
this.root = invertScannerFunctionRelationship(root);
3640
this.isRootPath = isRootPath;
3741
}
3842

@@ -41,6 +45,48 @@ public boolean isRootPath() {
4145
return isRootPath;
4246
}
4347

48+
49+
50+
/**
51+
* In the event the writer of the path referenced a function at the tail end of a scanner, augment the query such
52+
* that the root node is the function and the parameter to the function is the scanner. This way we maintain
53+
* relative sanity in the path expression, functions either evaluate scalar values or arrays, they're
54+
* not re-entrant nor should they maintain state, they do however take parameters.
55+
*
56+
* @param path
57+
* this is our old root path which will become a parameter (assuming there's a scanner terminated by a function
58+
*
59+
* @return
60+
* A function with the scanner as input, or if this situation doesn't exist just the input path
61+
*/
62+
private RootPathToken invertScannerFunctionRelationship(final RootPathToken path) {
63+
if (path.isFunctionPath() && path.next() instanceof ScanPathToken) {
64+
PathToken token = path;
65+
PathToken prior = null;
66+
while (null != (token = token.next()) && !(token instanceof FunctionPathToken)) {
67+
prior = token;
68+
}
69+
// Invert the relationship $..path.function() to $.function($..path)
70+
if (token instanceof FunctionPathToken) {
71+
prior.setNext(null);
72+
path.setTail(prior);
73+
74+
// Now generate a new parameter from our path
75+
Parameter parameter = new Parameter();
76+
parameter.setPath(new CompiledPath(path, true));
77+
parameter.setType(ParamType.PATH);
78+
((FunctionPathToken)token).setParameters(Arrays.asList(parameter));
79+
RootPathToken functionRoot = new RootPathToken('$');
80+
functionRoot.setTail(token);
81+
functionRoot.setNext(token);
82+
83+
// Define the function as the root
84+
return functionRoot;
85+
}
86+
}
87+
return path;
88+
}
89+
4490
@Override
4591
public EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration, boolean forUpdate) {
4692
if (logger.isDebugEnabled()) {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class FunctionPathToken extends PathToken {
2020

2121
private final String functionName;
2222
private final String pathFragment;
23-
private final List<Parameter> functionParams;
23+
private List<Parameter> functionParams;
2424

2525
public FunctionPathToken(String pathFragment, List<Parameter> parameters) {
2626
this.pathFragment = pathFragment + ((parameters != null && parameters.size() > 0) ? "(...)" : "()");
@@ -81,4 +81,7 @@ public String getPathFragment() {
8181
}
8282

8383

84+
public void setParameters(List<Parameter> parameters) {
85+
this.functionParams = parameters;
86+
}
8487
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,7 @@ public void invoke(PathFunction pathFunction, String currentPath, PathRef parent
215215

216216
protected abstract String getPathFragment();
217217

218+
public void setNext(final PathToken next) {
219+
this.next = next;
220+
}
218221
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,8 @@ public boolean isTokenDefinite() {
7676
public boolean isFunctionPath() {
7777
return (tail instanceof FunctionPathToken);
7878
}
79+
80+
public void setTail(PathToken token) {
81+
this.tail = token;
82+
}
7983
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.jayway.jsonpath.internal.function;
2+
3+
import com.jayway.jsonpath.Configuration;
4+
import com.jayway.jsonpath.Configurations;
5+
import com.jayway.jsonpath.JsonPath;
6+
import org.junit.Test;
7+
8+
import java.io.InputStream;
9+
10+
import static org.junit.Assert.assertEquals;
11+
12+
/**
13+
* TDD for Issue 191
14+
*
15+
* Shows aggregation across fields rather than within a single entity.
16+
*
17+
*/
18+
public class Issue191 {
19+
20+
private Configuration conf = Configurations.GSON_CONFIGURATION;
21+
22+
@Test
23+
public void testResultSetNumericComputation() {
24+
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
25+
Long value = JsonPath.parse(stream).read("$.sum($..timestamp)", Long.class);
26+
assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set",
27+
Long.valueOf(35679716813L), value);
28+
}
29+
30+
@Test
31+
public void testResultSetNumericComputationTail() {
32+
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
33+
Long value = JsonPath.parse(stream).read("$..timestamp.sum()", Long.class);
34+
assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set",
35+
Long.valueOf(35679716813L), value);
36+
}
37+
38+
@Test
39+
public void testResultSetNumericComputationRecursiveReplacement() {
40+
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
41+
Long value = JsonPath.parse(stream).read("$.max($..timestamp.avg(), $..timestamp.stddev())", Long.class);
42+
assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set",
43+
Long.valueOf(1427188672L), value);
44+
}
45+
46+
@Test
47+
public void testMultipleResultSetSums() {
48+
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
49+
Long value = JsonPath.parse(stream).read("$.sum($..timestamp, $..cpus)", Long.class);
50+
assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set",
51+
Long.valueOf(35679716835L), value);
52+
}
53+
54+
@Test
55+
public void testConcatResultSet() {
56+
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
57+
String concatResult = JsonPath.parse(stream).read("$.concat($..state)", String.class);
58+
assertEquals("Expected a string length to be a concat of all of the states", concatResult.length(), 806);
59+
}
60+
61+
@Test
62+
public void testConcatWithNumericValueAsString() {
63+
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
64+
String concatResult = JsonPath.parse(stream).read("$.concat($..cpus)", String.class);
65+
assertEquals("Expected a string length to be a concat of all of the cpus", concatResult.length(), 489);
66+
}
67+
}

0 commit comments

Comments
 (0)