diff --git a/README.md b/README.md index 13b228a9..6463a6a0 100644 --- a/README.md +++ b/README.md @@ -58,21 +58,23 @@ Functions Functions can be invoked at the tail end of a path - the input to a function is the output of the path expression. The function output is dictated by the function itself. -| Function | Description | Output type | -|:------------|:-------------------------------------------------------------------------------------|:---------------------| -| `min()` | Provides the min value of an array of numbers | Double | -| `max()` | Provides the max value of an array of numbers | Double | -| `avg()` | Provides the average value of an array of numbers | Double | -| `stddev()` | Provides the standard deviation value of an array of numbers | Double | -| `length()` | Provides the length of an array | Integer | -| `sum()` | Provides the sum value of an array of numbers | Double | -| `keys()` | Provides the property keys (An alternative for terminal tilde `~`) | `Set` | -| `concat(X)` | Provides a concatinated version of the path output with a new item | like input | -| `append(X)` | add an item to the json path output array | like input | -| `first()` | Provides the first item of an array | Depends on the array | -| `last()` | Provides the last item of an array | Depends on the array | -| `index(X)` | Provides the item of an array of index: X, if the X is negative, take from backwards | Depends on the array | -| `distinct()`| Provides an array containing only unique items from the input array | List | +| Function | Description | Output type | +|:------------|:---------------------------------------------------------------------------------------|:---------------------| +| `min()` | Provides the min value of an array of numbers | Double | +| `max()` | Provides the max value of an array of numbers | Double | +| `avg()` | Provides the average value of an array of numbers | Double | +| `stddev()` | Provides the standard deviation value of an array of numbers | Double | +| `length()` | Provides the length of an array | Integer | +| `sum()` | Provides the sum value of an array of numbers | Double | +| `keys()` | Provides the property keys (An alternative for terminal tilde `~`) | `Set` | +| `concat(X)` | Provides a concatinated version of the path output with a new item | like input | +| `append(X)` | add an item to the json path output array | like input | +| `first()` | Provides the first item of an array | Depends on the array | +| `last()` | Provides the last item of an array | Depends on the array | +| `index(X)` | Provides the item of an array of index: X, if the X is negative, take from backwards | Depends on the array | +| `distinct()`| Provides an array containing only unique items from the input array | List | +| `random()` | Provides one random item from the array | Depends on the array | + Filter Operators ----------------- 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 0731d022..b2808b37 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 @@ -12,6 +12,7 @@ import com.jayway.jsonpath.internal.function.sequence.First; import com.jayway.jsonpath.internal.function.sequence.Index; import com.jayway.jsonpath.internal.function.sequence.Last; +import com.jayway.jsonpath.internal.function.sequence.Random; import com.jayway.jsonpath.internal.function.text.Concatenate; import com.jayway.jsonpath.internal.function.text.Length; @@ -56,6 +57,7 @@ public class PathFunctionFactory { map.put("last", Last.class); map.put("distinct", Distinct.class); map.put("index", Index.class); + map.put("random", Random.class); FUNCTIONS = Collections.unmodifiableMap(map); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Random.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Random.java new file mode 100644 index 00000000..bf2b5de1 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Random.java @@ -0,0 +1,34 @@ +package com.jayway.jsonpath.internal.function.sequence; + +import com.jayway.jsonpath.JsonPathException; +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.security.SecureRandom; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * Take a random item from collection of JSONArray + */ +public class Random implements PathFunction { + @Override + public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) { + if (ctx.configuration().jsonProvider().isArray(model)) { + Iterable objects = ctx.configuration().jsonProvider().toIterable(model); + List elements = StreamSupport.stream(objects.spliterator(), false) + .collect(Collectors.toList()); + + if (elements.isEmpty()) { + throw new JsonPathException("Aggregation function attempted to get a random item from empty array"); + } + + SecureRandom rand = new SecureRandom(); + return elements.get(rand.nextInt(elements.size())); + } + throw new JsonPathException("Aggregation function attempted to get a random item from not array"); + } +} 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 1154a26d..a563a63e 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 @@ -40,8 +40,12 @@ public class BaseFunctionTest { * The expected value to be returned from the test */ protected void verifyFunction(Configuration conf, String pathExpr, String json, Object expectedValue) { + assertThat(evaluate(conf, pathExpr, json)).isEqualTo(expectedValue); + } + + protected Object evaluate(Configuration conf, String pathExpr, String json) { Object result = using(conf).parse(json).read(pathExpr); - assertThat(conf.jsonProvider().unwrap(result)).isEqualTo(expectedValue); + return conf.jsonProvider().unwrap(result); } protected void verifyMathFunction(Configuration conf, String pathExpr, Object expectedValue) { diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/SequentialPathFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/SequentialPathFunctionTest.java index 97c1eaeb..2cf1db20 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/internal/function/SequentialPathFunctionTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/SequentialPathFunctionTest.java @@ -1,11 +1,13 @@ package com.jayway.jsonpath.internal.function; import static com.jayway.jsonpath.JsonPath.using; +import static org.assertj.core.api.Assertions.assertThat; import com.fasterxml.jackson.databind.ObjectMapper; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.Configurations; import java.util.Arrays; +import java.util.List; import com.jayway.jsonpath.spi.json.JacksonJsonProvider; import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; @@ -18,6 +20,7 @@ * -last * -index(X) * -distinct + * -random * * Created by git9527 on 6/11/22. */ @@ -90,4 +93,18 @@ public void testDistinctOfEmptyObjects() throws Exception { final Object expectedValue = Configuration.defaultConfiguration().jsonProvider().parse("[]"); verifyFunction(conf, "$.empty.distinct()", OBJECT_SERIES, expectedValue); } + + @Test + public void testRandomForStrings() throws Exception { + List elements = (List) evaluate(conf, "$.text", TEXT_AND_NUMBER_SERIES); + Object randomElement = evaluate(conf, "$.text.random()", TEXT_AND_NUMBER_SERIES); + assertThat(randomElement).isInstanceOf(String.class).isIn(elements); + } + + @Test + public void testRandomForNumbers() throws Exception { + List elements = (List) evaluate(conf, "$.numbers", TEXT_AND_NUMBER_SERIES); + Object randomElement = evaluate(conf, "$.numbers.random()", TEXT_AND_NUMBER_SERIES); + assertThat(randomElement).isInstanceOf(Integer.class).isIn(elements); + } }