From 9729cb048b390b8db884ddc2c5b50afd82828c61 Mon Sep 17 00:00:00 2001 From: zhangsn <398816613@qq.com> Date: Thu, 27 Oct 2022 17:39:54 +0800 Subject: [PATCH] Defines the pattern for taking item from collection of JSONArray by index (#842) --- README.md | 26 ++++----- .../function/PathFunctionFactory.java | 8 +++ .../sequence/AbstractSequenceAggregation.java | 47 ++++++++++++++++ .../internal/function/sequence/First.java | 18 +++++++ .../internal/function/sequence/Index.java | 18 +++++++ .../internal/function/sequence/Last.java | 18 +++++++ .../jsonpath/internal/path/PathCompiler.java | 4 +- .../function/SequentialPathFunctionTest.java | 53 +++++++++++++++++++ 8 files changed, 178 insertions(+), 14 deletions(-) create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/AbstractSequenceAggregation.java create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/First.java create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Index.java create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Last.java create mode 100644 json-path/src/test/java/com/jayway/jsonpath/internal/function/SequentialPathFunctionTest.java diff --git a/README.md b/README.md index 5988c198..0b1e72a1 100644 --- a/README.md +++ b/README.md @@ -83,18 +83,20 @@ 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 | - +| 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 | 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 2fcd3a33..3a31151f 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 @@ -8,6 +8,9 @@ import com.jayway.jsonpath.internal.function.numeric.Max; import com.jayway.jsonpath.internal.function.numeric.Min; import com.jayway.jsonpath.internal.function.numeric.StandardDeviation; import com.jayway.jsonpath.internal.function.numeric.Sum; +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.text.Concatenate; import com.jayway.jsonpath.internal.function.text.Length; @@ -46,6 +49,11 @@ public class PathFunctionFactory { map.put("size", Length.class); map.put("append", Append.class); map.put("keys", KeySetFunction.class); + + // Sequential Functions + map.put("first", First.class); + map.put("last", Last.class); + map.put("index", Index.class); FUNCTIONS = Collections.unmodifiableMap(map); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/AbstractSequenceAggregation.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/AbstractSequenceAggregation.java new file mode 100644 index 00000000..24c87e13 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/AbstractSequenceAggregation.java @@ -0,0 +1,47 @@ +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.util.ArrayList; +import java.util.List; + +/** + * Defines the pattern for taking item from collection of JSONArray by index + * + * Created by git9527 on 6/11/22. + */ +public abstract class AbstractSequenceAggregation implements PathFunction { + + protected abstract int targetIndex(EvaluationContext ctx, List parameters); + + @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 objectList = new ArrayList<>(); + objects.forEach(objectList::add); + int targetIndex = this.targetIndex(ctx, parameters); + if (targetIndex >= 0) { + return objectList.get(targetIndex); + } else { + int realIndex = objectList.size() + targetIndex; + if (realIndex > 0) { + return objectList.get(realIndex); + } else { + throw new JsonPathException("Target index:" + targetIndex + " larger than object count:" + objectList.size()); + } + } + } + throw new JsonPathException("Aggregation function attempted to calculate value using empty array"); + } + + protected int getIndexFromParameters(EvaluationContext ctx, List parameters) { + List numbers = Parameter.toList(Number.class, ctx, parameters); + return numbers.get(0).intValue(); + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/First.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/First.java new file mode 100644 index 00000000..4f92ef25 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/First.java @@ -0,0 +1,18 @@ +package com.jayway.jsonpath.internal.function.sequence; + +import com.jayway.jsonpath.internal.EvaluationContext; +import com.jayway.jsonpath.internal.function.Parameter; + +import java.util.List; + +/** + * Take the first item from collection of JSONArray + * + * Created by git9527 on 6/11/22. + */ +public class First extends AbstractSequenceAggregation { + @Override + protected int targetIndex(EvaluationContext ctx, List parameters) { + return 0; + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Index.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Index.java new file mode 100644 index 00000000..b0ebd8a6 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Index.java @@ -0,0 +1,18 @@ +package com.jayway.jsonpath.internal.function.sequence; + +import com.jayway.jsonpath.internal.EvaluationContext; +import com.jayway.jsonpath.internal.function.Parameter; + +import java.util.List; + +/** + * Take the index from first Parameter, then the item from collection of JSONArray by index + * + * Created by git9527 on 6/11/22. + */ +public class Index extends AbstractSequenceAggregation { + @Override + protected int targetIndex(EvaluationContext ctx, List parameters) { + return getIndexFromParameters(ctx, parameters); + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Last.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Last.java new file mode 100644 index 00000000..39b6345e --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Last.java @@ -0,0 +1,18 @@ +package com.jayway.jsonpath.internal.function.sequence; + +import com.jayway.jsonpath.internal.EvaluationContext; +import com.jayway.jsonpath.internal.function.Parameter; + +import java.util.List; + +/** + * Take the first item from collection of JSONArray + * + * Created by git9527 on 6/11/22. + */ +public class Last extends AbstractSequenceAggregation { + @Override + protected int targetIndex(EvaluationContext ctx, List parameters) { + return -1; + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java index 72f68126..efb59135 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java @@ -305,7 +305,7 @@ public class PathCompiler { continue; } - if (c == OPEN_BRACE || isDigit(c) || DOUBLE_QUOTE == c) { + if (c == OPEN_BRACE || isDigit(c) || DOUBLE_QUOTE == c || MINUS == c) { type = ParamType.JSON; } else if (isPathContext(c)) { @@ -612,7 +612,7 @@ public class PathCompiler { inProperty = true; lastSignificantWasComma = false; } - } else if (c == COMMA && !inProperty) { + } else if (c == COMMA && !inProperty) { if (lastSignificantWasComma){ fail("Found empty property at index "+readPosition); } 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 new file mode 100644 index 00000000..70b2b953 --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/SequentialPathFunctionTest.java @@ -0,0 +1,53 @@ +package com.jayway.jsonpath.internal.function; + +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.Configurations; +import org.junit.Test; + +/** + * Test cases for functions + * + * -first + * -last + * -index(X) + * + * Created by git9527 on 6/11/22. + */ +public class SequentialPathFunctionTest extends BaseFunctionTest { + + private Configuration conf = Configurations.JACKSON_CONFIGURATION; + + @Test + public void testFirstOfNumbers() throws Exception { + verifyFunction(conf, "$.numbers.first()", NUMBER_SERIES, 1); + } + + @Test + public void testLastOfNumbers() throws Exception { + verifyFunction(conf, "$.numbers.last()", NUMBER_SERIES, 10); + } + + @Test + public void testIndexOfNumbers() throws Exception { + verifyFunction(conf, "$.numbers.index(0)", NUMBER_SERIES, 1); + verifyFunction(conf, "$.numbers.index(-1)", NUMBER_SERIES, 10); + verifyFunction(conf, "$.numbers.index(1)", NUMBER_SERIES, 2); + } + + @Test + public void testFirstOfText() throws Exception { + verifyFunction(conf, "$.text.first()", TEXT_SERIES, "a"); + } + + @Test + public void testLastOfText() throws Exception { + verifyFunction(conf, "$.text.last()", TEXT_SERIES, "f"); + } + + @Test + public void testIndexOfText() throws Exception { + verifyFunction(conf, "$.text.index(0)", TEXT_SERIES, "a"); + verifyFunction(conf, "$.text.index(-1)", TEXT_SERIES, "f"); + verifyFunction(conf, "$.text.index(1)", TEXT_SERIES, "b"); + } +}