diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java index 5dd5b836..921f9e98 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java @@ -1,6 +1,7 @@ package com.jayway.jsonpath.internal.function; import com.jayway.jsonpath.InvalidPathException; +import com.jayway.jsonpath.internal.function.http.HttpLoader; import com.jayway.jsonpath.internal.function.numeric.Average; import com.jayway.jsonpath.internal.function.numeric.Max; import com.jayway.jsonpath.internal.function.numeric.Min; @@ -40,6 +41,9 @@ public class PathFunctionFactory { // Text Functions map.put("concat", Concatenate.class); + // Network functions + map.put("getjson", HttpLoader.class); + // JSON Entity Functions map.put("length", Length.class); map.put("size", Length.class); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/http/HttpLoader.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/http/HttpLoader.java new file mode 100644 index 00000000..acf58f07 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/http/HttpLoader.java @@ -0,0 +1,49 @@ +package com.jayway.jsonpath.internal.function.http; + +import com.jayway.jsonpath.internal.EvaluationContext; +import com.jayway.jsonpath.internal.PathRef; +import com.jayway.jsonpath.internal.function.Parameter; +import com.jayway.jsonpath.internal.function.PathFunction; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.URL; +import java.util.List; + +/** + * Dirt simple http get method just to demo URL loading + * + * Created by mgreenwood on 12/11/15. + */ +public class HttpLoader implements PathFunction { + @Override + public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) { + if (parameters != null && parameters.size() == 1) { + try { + URL url = new URL(parameters.get(0).getCachedValue().toString()); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + StringBuffer result = new StringBuffer(); + while ((line = rd.readLine()) != null) { + result.append(line); + } + rd.close(); + Object jsonResult = ctx.configuration().jsonProvider().parse(result.toString()); + return jsonResult; + } catch (ProtocolException e) { + e.printStackTrace(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java index 8af57b44..78d9da99 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java @@ -1,5 +1,6 @@ package com.jayway.jsonpath.internal.function.numeric; +import com.jayway.jsonpath.JsonPathException; import com.jayway.jsonpath.internal.EvaluationContext; import com.jayway.jsonpath.internal.PathRef; import com.jayway.jsonpath.internal.function.Parameter; @@ -34,12 +35,14 @@ public abstract class AbstractAggregation implements PathFunction { @Override public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) { + int count = 0; if(ctx.configuration().jsonProvider().isArray(model)){ Iterable objects = ctx.configuration().jsonProvider().toIterable(model); for (Object obj : objects) { if (obj instanceof Number) { Number value = (Number) obj; + count++; next(value); } } @@ -48,10 +51,14 @@ public abstract class AbstractAggregation implements PathFunction { for (Parameter param : parameters) { if (param.getCachedValue() instanceof Number) { Number value = (Number)param.getCachedValue(); + count++; next(value); } } } - return getValue(); + if (count != 0) { + return getValue(); + } + throw new JsonPathException("Aggregation function attempted to calculate value using empty array"); } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java index 804d1e96..fa43f365 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java @@ -130,7 +130,7 @@ public class EvaluationContextImpl implements EvaluationContext { if(resultIndex == 0){ throw new PathNotFoundException("No results for path: " + path.toString()); } - Object value = jsonProvider().getArrayIndex(valueResult, 0); + Object value = jsonProvider().getLastElement(valueResult); if (value != null && unwrap){ value = jsonProvider().unwrap(value); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java index 5fe851e1..ad8bd421 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java @@ -36,7 +36,10 @@ public class FunctionPathToken extends PathToken { PathFunction pathFunction = PathFunctionFactory.newFunction(functionName); evaluateParameters(currentPath, parent, model, ctx); Object result = pathFunction.invoke(currentPath, parent, model, ctx, functionParams); - ctx.addResult(currentPath, parent, result); + ctx.addResult(currentPath + "." + functionName, parent, result); + if (!isLeaf()) { + next().evaluate(currentPath, parent, result, ctx); + } } private void evaluateParameters(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java index 56cfb47c..71872db7 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java @@ -43,6 +43,20 @@ public abstract class AbstractJsonProvider implements JsonProvider { return ((List) obj).get(idx); } + /** + * Extracts the last value from an array + * + * @param obj an array + * @return the entry at the given index + */ + public Object getLastElement(Object obj) { + if (null != obj) { + int len = this.length(obj); + return unwrap(getArrayIndex(obj, len-1)); + } + return null; + } + public final Object getArrayIndex(Object obj, int idx, boolean unwrap){ return getArrayIndex(obj, idx); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java index 92f63ccd..e1caa582 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java @@ -165,4 +165,11 @@ public interface JsonProvider { Object unwrap(Object obj); + /** + * Get the last element of the array + * + * @param obj an array + * @return the unwrapped value + */ + Object getLastElement(Object obj); } 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 8f67608f..6514174b 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 @@ -12,8 +12,8 @@ import static org.assertj.core.api.Assertions.assertThat; * Created by mattg on 6/27/15. */ public class BaseFunctionTest { - protected static final String NUMBER_SERIES = "{\"numbers\" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}"; - protected static final String TEXT_SERIES = "{\"text\" : [ \"a\", \"b\", \"c\", \"d\", \"e\", \"f\" ]}"; + 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\" ]}"; diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java index b4c13502..38f50e68 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java @@ -64,4 +64,9 @@ public class NestedFunctionTest extends BaseFunctionTest { public void testStringConcatWithJSONParameter() { verifyTextFunction(conf, "$.text.concat(\"-\", \"ghijk\")", "abcdef-ghijk"); } + + @Test + public void testLoadFunction() { + verifyTextFunction(conf, "$.getjson($.urls[0])[0].total", 264); + } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java index f3903aa0..54814091 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java @@ -2,12 +2,14 @@ package com.jayway.jsonpath.internal.function; 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; +import static org.junit.Assert.assertEquals; import static org.junit.runners.Parameterized.Parameters; /** @@ -45,26 +47,72 @@ public class NumericPathFunctionTest extends BaseFunctionTest { verifyMathFunction(conf, "$.numbers.avg()", 5.5); } + @Test + public void testAverageOfEmptyListNegative() { + try { + verifyMathFunction(conf, "$.empty.avg()", null); + } catch (JsonPathException e) { + assertEquals(e.getMessage(), "Aggregation function attempted to calculate value using empty array"); + } + } + @Test public void testSumOfDouble() { verifyMathFunction(conf, "$.numbers.sum()", (10d * (10d + 1d)) / 2d); } + @Test + public void testSumOfEmptyListNegative() { + try { + verifyMathFunction(conf, "$.empty.sum()", null); + } catch (JsonPathException e) { + assertEquals(e.getMessage(), "Aggregation function attempted to calculate value using empty array"); + } + } + @Test public void testMaxOfDouble() { verifyMathFunction(conf, "$.numbers.max()", 10d); } + @Test + public void testMaxOfEmptyListNegative() { + try { + verifyMathFunction(conf, "$.empty.max()", null); + } catch (JsonPathException e) { + assertEquals(e.getMessage(), "Aggregation function attempted to calculate value using empty array"); + } + } + @Test public void testMinOfDouble() { verifyMathFunction(conf, "$.numbers.min()", 1d); } + @Test + public void testMinOfEmptyListNegative() { + try { + verifyMathFunction(conf, "$.empty.min()", null); + } catch (JsonPathException e) { + assertEquals(e.getMessage(), "Aggregation function attempted to calculate value using empty array"); + } + } + + @Test public void testStdDevOfDouble() { verifyMathFunction(conf, "$.numbers.stddev()", 2.8722813232690143d); } + @Test + public void testStddevOfEmptyListNegative() { + try { + verifyMathFunction(conf, "$.empty.stddev()", null); + } catch (JsonPathException e) { + assertEquals(e.getMessage(), "Aggregation function attempted to calculate value using empty array"); + } + } + /** * Expect that for an invalid function name we'll get back the original input to the function */