diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java index 018a8b5e..039463ae 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java @@ -50,7 +50,25 @@ public class CompiledPath implements Path { EvaluationContextImpl ctx = new EvaluationContextImpl(this, rootDocument, configuration, forUpdate); try { PathRef op = ctx.forUpdate() ? PathRef.createRoot(rootDocument) : PathRef.NO_OP; - root.evaluate("", op, document, ctx); + + if (root.isFunctionPath()) { + // Remove the functionPath and evaluate the resulting path. + PathToken funcToken = root.chop(); + root.evaluate("", op, document, ctx); + // Get the value of the evaluation to use as model when evaluating the function. + Object arrayModel = ctx.getValue(false); + + // Evaluate the function on the model from the first evaluation. + RootPathToken newRoot = new RootPathToken('x'); + newRoot.append(funcToken); + CompiledPath newCPath = new CompiledPath(newRoot, true); + EvaluationContextImpl newCtx = new EvaluationContextImpl(newCPath, arrayModel, configuration, false); + funcToken.evaluate("", op, arrayModel, newCtx); + return newCtx; + } else { + root.evaluate("", op, document, ctx); + return ctx; + } } catch (EvaluationAbortException abort){}; return ctx; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java index dc22dda1..96583de3 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java @@ -36,6 +36,15 @@ public abstract class PathToken { return next; } + PathToken remove() { + prev.next = next; + if (next == null) { + return prev; + } + next.prev = prev; + return next; + } + void handleObjectProperty(String currentPath, Object model, EvaluationContextImpl ctx, List properties) { if(properties.size() == 1) { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java index 2b2db7b1..87fcfbab 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java @@ -43,6 +43,13 @@ public class RootPathToken extends PathToken { return this; } + public PathToken chop() { + // Remove tail. + PathToken oldTail = tail; + tail = tail.remove(); + return oldTail; + } + public PathTokenAppender getPathTokenAppender(){ return new PathTokenAppender(){ @Override diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/BaseFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/BaseFunctionTest.java index 8962b38f..48d6de3f 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/internal/function/BaseFunctionTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/BaseFunctionTest.java @@ -14,6 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class BaseFunctionTest { protected static final String NUMBER_SERIES = "{\"empty\": [], \"numbers\" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}"; protected static final String TEXT_SERIES = "{\"urls\": [\"http://api.worldbank.org/countries/all/?format=json\", \"http://api.worldbank.org/countries/all/?format=json\"], \"text\" : [ \"a\", \"b\", \"c\", \"d\", \"e\", \"f\" ]}"; + protected static final String EXAMPLE_SERIES = "{\"store\":{\"book\":[{\"category\":\"reference\",\"author\":\"Nigel Rees\",\"title\":\"Sayings of the Century\",\"price\":8.95},{\"category\":\"fiction\",\"author\":\"Evelyn Waugh\",\"title\":\"Sword of Honour\",\"price\":12.99},{\"category\":\"fiction\",\"author\":\"Herman Melville\",\"title\":\"Moby Dick\",\"isbn\":\"0-553-21311-3\",\"price\":8.99},{\"category\":\"fiction\",\"author\":\"J. R. R. Tolkien\",\"title\":\"The Lord of the Rings\",\"isbn\":\"0-395-19395-8\",\"price\":22.99}],\"bicycle\":{\"color\":\"red\",\"price\":19.95}},\"expensive\":10}"; /** * Verify the function returns the correct result based on the input expectedValue @@ -40,6 +41,10 @@ public class BaseFunctionTest { verifyFunction(conf, pathExpr, TEXT_SERIES, expectedValue); } + protected void verifyExampleFunction(Configuration conf, String pathExpr, Object expectedValue) { + verifyFunction(conf, pathExpr, EXAMPLE_SERIES, expectedValue); + } + protected String getResourceAsText(String resourceName) throws IOException { return new Scanner(BaseFunctionTest.class.getResourceAsStream(resourceName), "UTF-8").useDelimiter("\\A").next(); } diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/ResultSetFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/ResultSetFunctionTest.java new file mode 100644 index 00000000..204cc560 --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/ResultSetFunctionTest.java @@ -0,0 +1,45 @@ +package com.jayway.jsonpath.internal.function; + +import static org.junit.Assert.assertEquals; +import static org.junit.runners.Parameterized.Parameters; + +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.Configurations; +import com.jayway.jsonpath.JsonPathException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Defines functional tests around executing functions on result sets. + */ +@RunWith(Parameterized.class) +public class ResultSetFunctionTest extends BaseFunctionTest { + + private static final Logger logger = LoggerFactory.getLogger(ResultSetFunctionTest.class); + + private Configuration conf = Configurations.GSON_CONFIGURATION; + + public ResultSetFunctionTest(Configuration conf) { + logger.debug("Testing with configuration {}", conf.getClass().getName()); + this.conf = conf; + } + + @Parameters + public static Iterable configurations() { + return Configurations.configurations(); + } + + @Test + public void testMaxOfDoublesResultSet() { + verifyExampleFunction(conf, "$.store.book[*].price.max()", 22.99); + } + + @Test + public void testSumOfDoublesResultSet() { + verifyExampleFunction(conf, "$.store.book[*].price.sum()", 53.92); + } + +}