This is an automated email from the ASF dual-hosted git repository.
fschumacher pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jmeter.git
The following commit(s) were added to refs/heads/master by this push:
new 0c97fd6 JSON Extractor and JSON JMESPath Extractor ignore sub-samples
0c97fd6 is described below
commit 0c97fd659f24960a89ff3344b7f71be9e70f1d28
Author: Felix Schumacher <[email protected]>
AuthorDate: Sun May 30 20:39:18 2021 +0200
JSON Extractor and JSON JMESPath Extractor ignore sub-samples
The UI for those elements suggest, that the extractors would
look into sub-samples for matches (or even could be configured
to use only values from the sub-samples). Adapt the implementation
to make that assumption true.
Bugzilla Id: 65269
---
.../extractor/json/jmespath/JMESPathExtractor.java | 48 +++++++-----
.../extractor/json/jsonpath/JSONPostProcessor.java | 27 ++++---
.../jmeter/extractor/TestJSONPostProcessor.java | 88 +++++++++++++++++++---
.../json/jmespath/TestJMESPathExtractor.java | 73 ++++++++++++++++++
xdocs/changes.xml | 1 +
xdocs/usermanual/component_reference.xml | 21 ++++--
6 files changed, 213 insertions(+), 45 deletions(-)
diff --git
a/src/components/src/main/java/org/apache/jmeter/extractor/json/jmespath/JMESPathExtractor.java
b/src/components/src/main/java/org/apache/jmeter/extractor/json/jmespath/JMESPathExtractor.java
index 78e72da..8cad7e6 100644
---
a/src/components/src/main/java/org/apache/jmeter/extractor/json/jmespath/JMESPathExtractor.java
+++
b/src/components/src/main/java/org/apache/jmeter/extractor/json/jmespath/JMESPathExtractor.java
@@ -20,7 +20,10 @@ package org.apache.jmeter.extractor.json.jmespath;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.processor.PostProcessor;
@@ -38,6 +41,8 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
+import io.burt.jmespath.Expression;
+
/**
* JMESPATH based extractor
*
@@ -60,7 +65,7 @@ public class JMESPathExtractor extends
AbstractScopedTestElement
public void process() {
JMeterContext context = getThreadContext();
JMeterVariables vars = context.getVariables();
- String jsonResponse = getData(vars, context);
+ List<String> jsonResponse = getData(vars, context);
String refName = getRefName();
String defaultValue = getDefaultValue();
int matchNumber;
@@ -71,21 +76,25 @@ public class JMESPathExtractor extends
AbstractScopedTestElement
}
final String jsonPathExpression = getJmesPathExpression().trim();
clearOldRefVars(vars, refName);
- if (StringUtils.isEmpty(jsonResponse)) {
+ if (jsonResponse.isEmpty()) {
handleEmptyResponse(vars, refName, defaultValue);
return;
}
try {
- JsonNode actualObj = OBJECT_MAPPER.readValue(jsonResponse,
JsonNode.class);
- JsonNode result =
JMESPathCache.getInstance().get(jsonPathExpression).search(actualObj);
- if (result.isNull()) {
- handleNullResult(vars, refName, defaultValue, matchNumber);
- return;
+ List<String> resultList = new ArrayList<>();
+ Expression<JsonNode> searchExpression =
JMESPathCache.getInstance().get(jsonPathExpression);
+ for (String response: jsonResponse) {
+ JsonNode actualObj = OBJECT_MAPPER.readValue(response,
JsonNode.class);
+ JsonNode result = searchExpression.search(actualObj);
+ if (result.isNull()) {
+ continue;
+ }
+ resultList.addAll(splitJson(result));
}
- List<String> resultList = splitJson(result);
// if more than one value extracted, suffix with "_index"
- if (resultList.size() > 1) {
+ int size = resultList.size();
+ if (size > 1) {
handleListResult(vars, refName, defaultValue, matchNumber,
resultList);
} else if (resultList.isEmpty()){
handleNullResult(vars, refName, defaultValue, matchNumber);
@@ -94,7 +103,7 @@ public class JMESPathExtractor extends
AbstractScopedTestElement
// else just one value extracted
handleSingleResult(vars, refName, matchNumber, resultList);
}
- vars.put(refName + REF_MATCH_NR,
Integer.toString(resultList.size()));
+ vars.put(refName + REF_MATCH_NR, Integer.toString(size));
} catch (Exception e) {
// if something wrong, default value added
if (log.isDebugEnabled()) {
@@ -155,23 +164,26 @@ public class JMESPathExtractor extends
AbstractScopedTestElement
vars.put(refName, defaultValue);
}
- private String getData(JMeterVariables vars, JMeterContext context) {
- String jsonResponse = null;
+ private List<String> getData(JMeterVariables vars, JMeterContext context) {
if (isScopeVariable()) {
- jsonResponse = vars.get(getVariableName());
+ String jsonResponse = vars.get(getVariableName());
if (log.isDebugEnabled()) {
log.debug("JMESExtractor is using variable: {}, which content
is: {}", getVariableName(), jsonResponse);
}
+ return Arrays.asList(jsonResponse);
} else {
SampleResult previousResult = context.getPreviousResult();
if (previousResult != null) {
- jsonResponse = previousResult.getResponseDataAsString();
- }
- if (log.isDebugEnabled()) {
- log.debug("JMESExtractor {} working on Response: {}",
getName(), jsonResponse);
+ List<String> results = getSampleList(previousResult).stream()
+ .map(SampleResult::getResponseDataAsString)
+ .collect(Collectors.toList());
+ if (log.isDebugEnabled()) {
+ log.debug("JMESExtractor {} working on Responses: {}",
getName(), results);
+ }
+ return results;
}
}
- return jsonResponse;
+ return Collections.emptyList();
}
public List<String> splitJson(JsonNode jsonNode) throws IOException {
diff --git
a/src/components/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java
b/src/components/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java
index 56bbe2e..6e61f73 100644
---
a/src/components/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java
+++
b/src/components/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java
@@ -18,10 +18,12 @@
package org.apache.jmeter.extractor.json.jsonpath;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
-import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.processor.PostProcessor;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.AbstractScopedTestElement;
@@ -62,7 +64,7 @@ public class JSONPostProcessor
public void process() {
JMeterContext context = getThreadContext();
JMeterVariables vars = context.getVariables();
- String jsonResponse = extractJsonResponse(context, vars);
+ List<String> jsonResponses = extractJsonResponse(context, vars);
String[] refNames = getRefNames().split(SEPARATOR);
String[] jsonPathExpressions =
getJsonPathExpressions().split(SEPARATOR);
String[] defaultValues = getDefaultValues().split(SEPARATOR);
@@ -76,11 +78,13 @@ public class JSONPostProcessor
String currentJsonPath = jsonPathExpressions[i].trim();
clearOldRefVars(vars, currentRefName);
try {
- if (StringUtils.isEmpty(jsonResponse)) {
+ if (jsonResponses.isEmpty()) {
handleEmptyResponse(vars, defaultValues, i,
currentRefName);
} else {
- List<Object> extractedValues = localMatcher.get()
- .extractWithJsonPath(jsonResponse,
currentJsonPath);
+ List<Object> extractedValues = new ArrayList<>();
+ for (String jsonResponse: jsonResponses) {
+
extractedValues.addAll(localMatcher.get().extractWithJsonPath(jsonResponse,
currentJsonPath));
+ }
// if no values extracted, default value added
if (extractedValues.isEmpty()) {
handleEmptyResult(vars, defaultValues, i, matchNumber,
currentRefName);
@@ -198,23 +202,26 @@ public class JSONPostProcessor
vars.put(currentRefName, defaultValues[i]);
}
- private String extractJsonResponse(JMeterContext context, JMeterVariables
vars) {
+ private List<String> extractJsonResponse(JMeterContext context,
JMeterVariables vars) {
String jsonResponse = "";
if (isScopeVariable()) {
- jsonResponse = vars.get(getVariableName());
if (log.isDebugEnabled()) {
log.debug("JSON Extractor is using variable: {}, which content
is: {}", getVariableName(), jsonResponse);
}
+ return Arrays.asList(vars.get(getVariableName()));
} else {
SampleResult previousResult = context.getPreviousResult();
if (previousResult != null) {
- jsonResponse = previousResult.getResponseDataAsString();
+ List<String> results = getSampleList(previousResult).stream()
+ .map(SampleResult::getResponseDataAsString)
+ .collect(Collectors.toList());
if (log.isDebugEnabled()) {
- log.debug("JSON Extractor {} working on Response: {}",
getName(), jsonResponse);
+ log.debug("JSON Extractor {} working on Responses: {}",
getName(), results);
}
+ return results;
}
}
- return jsonResponse;
+ return Collections.emptyList();
}
private void clearOldRefVars(JMeterVariables vars, String refName) {
diff --git
a/src/components/src/test/java/org/apache/jmeter/extractor/TestJSONPostProcessor.java
b/src/components/src/test/java/org/apache/jmeter/extractor/TestJSONPostProcessor.java
index 99ee282..acc6775 100644
---
a/src/components/src/test/java/org/apache/jmeter/extractor/TestJSONPostProcessor.java
+++
b/src/components/src/test/java/org/apache/jmeter/extractor/TestJSONPostProcessor.java
@@ -21,24 +21,80 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.nio.charset.StandardCharsets;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor;
import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jmeter.testelement.AbstractScopedTestElement;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
-public class TestJSONPostProcessor {
+class TestJSONPostProcessor {
private static final String VAR_NAME = "varName";
+ private enum AccessMode {
+ ALL(AbstractScopedTestElement::setScopeAll),
+ PARENT(AbstractScopedTestElement::setScopeParent),
+ CHILDREN(AbstractScopedTestElement::setScopeChildren);
+
+ private Consumer<AbstractScopedTestElement> applier;
+
+ AccessMode(Consumer<AbstractScopedTestElement> applier) {
+ this.applier = applier;
+ }
+
+ void configure(AbstractScopedTestElement element) {
+ applier.accept(element);
+ }
+ }
+
+ private static Stream<Arguments> provideArgumentsForScopes() {
+ return Stream.of(
+ Arguments.of(AccessMode.ALL, "$.a", "1", "23", "2"),
+ Arguments.of(AccessMode.ALL, "$.a", "2", "42", "2"),
+ Arguments.of(AccessMode.ALL, "$.b", "0", "parent_only", "1"),
+ Arguments.of(AccessMode.ALL, "$.c", "0", "child_only", "1"),
+ Arguments.of(AccessMode.PARENT, "$.a", "1", "23", "1"),
+ Arguments.of(AccessMode.PARENT, "$.b", "0", "parent_only",
"1"),
+ Arguments.of(AccessMode.PARENT, "$.c", "0", "NONE", "0"),
+ Arguments.of(AccessMode.CHILDREN, "$.a", "1", "42", "1"),
+ Arguments.of(AccessMode.CHILDREN, "$.b", "0", "NONE", "0"),
+ Arguments.of(AccessMode.CHILDREN, "$.c", "0", "child_only",
"1")
+ );
+ }
+
+ @ParameterizedTest()
+ @MethodSource("provideArgumentsForScopes")
+ void testAssertionWithScope(AccessMode accessMode, String path, String
matchNumber, String resultObject,
+ String resultCount) {
+ JMeterContext context = JMeterContextService.getContext();
+ JSONPostProcessor processor = setupProcessor(context, matchNumber,
false);
+ JMeterVariables vars = new JMeterVariables();
+ processor.setDefaultValues("NONE");
+ processor.setJsonPathExpressions(path);
+ processor.setRefNames("result");
+ accessMode.configure(processor);
+ SampleResult sampleResult = createSampleResult("{\"a\": 23, \"b\":
\"parent_only\"}");
+ sampleResult.addSubResult(createSampleResult("{\"a\": 42, \"c\":
\"child_only\"}"));
+ context.setPreviousResult(sampleResult);
+ context.setVariables(vars);
+ processor.process();
+ assertThat(vars.get("result"), CoreMatchers.is(resultObject));
+ }
+
@Test
- public void testProcessAllElementsOneMatch() {
+ void testProcessAllElementsOneMatch() {
JMeterContext context = JMeterContextService.getContext();
JSONPostProcessor processor = setupProcessor(context, "-1", true);
JMeterVariables vars = new JMeterVariables();
@@ -55,7 +111,7 @@ public class TestJSONPostProcessor {
}
@Test
- public void testProcessAllElementsMultipleMatches() {
+ void testProcessAllElementsMultipleMatches() {
JMeterContext context = JMeterContextService.getContext();
JSONPostProcessor processor = setupProcessor(context, "-1", true);
JMeterVariables vars = new JMeterVariables();
@@ -72,7 +128,7 @@ public class TestJSONPostProcessor {
}
@Test
- public void testProcessRandomElementMultipleMatches() {
+ void testProcessRandomElementMultipleMatches() {
JMeterContext context = JMeterContextService.getContext();
JSONPostProcessor processor = setupProcessor(context, "0", true);
JMeterVariables vars = new JMeterVariables();
@@ -90,7 +146,7 @@ public class TestJSONPostProcessor {
}
@Test
- public void testPR235CaseEmptyResponse() {
+ void testPR235CaseEmptyResponse() {
JMeterContext context = JMeterContextService.getContext();
JSONPostProcessor processor = setupProcessor(context, "-1", true);
JMeterVariables vars = new JMeterVariables();
@@ -112,7 +168,7 @@ public class TestJSONPostProcessor {
}
@Test
- public void testCaseEmptyVarBug62860() {
+ void testCaseEmptyVarBug62860() {
JMeterContext context = JMeterContextService.getContext();
JSONPostProcessor processor = setupProcessor(context, "0", false);
JMeterVariables vars = new JMeterVariables();
@@ -133,7 +189,7 @@ public class TestJSONPostProcessor {
}
@Test
- public void testPR235CaseMatchOneWithZero() {
+ void testPR235CaseMatchOneWithZero() {
JMeterContext context = JMeterContextService.getContext();
JSONPostProcessor processor = setupProcessor(context, "-1", true);
JMeterVariables vars = new JMeterVariables();
@@ -157,7 +213,7 @@ public class TestJSONPostProcessor {
}
@Test
- public void testBug59609() throws ParseException {
+ void testBug59609() throws ParseException {
JMeterContext context = JMeterContextService.getContext();
JSONPostProcessor processor = setupProcessor(context, "0", false);
@@ -181,7 +237,7 @@ public class TestJSONPostProcessor {
}
@Test
- public void testExtractSimpleArrayElements() {
+ void testExtractSimpleArrayElements() {
JMeterContext context = JMeterContextService.getContext();
JSONPostProcessor processor = setupProcessor(context, "-1");
String data = "[1,2,3]";
@@ -204,7 +260,7 @@ public class TestJSONPostProcessor {
}
@Test
- public void testExtractComplexElements() {
+ void testExtractComplexElements() {
JMeterContext context = JMeterContextService.getContext();
JSONPostProcessor processor = setupProcessor(context, "-1");
String data = "[{\"a\":[1,{\"d\":2},3]},[\"b\",{\"h\":23}],3]";
@@ -227,11 +283,18 @@ public class TestJSONPostProcessor {
assertEquals("3", vars.get(VAR_NAME + "_matchNr"));
}
- private JSONPostProcessor setupProcessor(JMeterContext context, String
matchNumbers) {
+
+ private static JSONPostProcessor setupProcessor(JMeterContext context,
String matchNumbers) {
return setupProcessor(context, matchNumbers, true);
}
- private JSONPostProcessor setupProcessor(JMeterContext context,
+ private static SampleResult createSampleResult(String data) {
+ SampleResult result = new SampleResult();
+ result.setResponseData(data, null);
+ return result;
+ }
+
+ private static JSONPostProcessor setupProcessor(JMeterContext context,
String matchNumbers, boolean computeConcatenation) {
JSONPostProcessor processor = new JSONPostProcessor();
processor.setThreadContext(context);
@@ -241,4 +304,5 @@ public class TestJSONPostProcessor {
return processor;
}
+
}
diff --git
a/src/components/src/test/java/org/apache/jmeter/extractor/json/jmespath/TestJMESPathExtractor.java
b/src/components/src/test/java/org/apache/jmeter/extractor/json/jmespath/TestJMESPathExtractor.java
index 87f72d0..c9f2b3e 100644
---
a/src/components/src/test/java/org/apache/jmeter/extractor/json/jmespath/TestJMESPathExtractor.java
+++
b/src/components/src/test/java/org/apache/jmeter/extractor/json/jmespath/TestJMESPathExtractor.java
@@ -21,8 +21,10 @@ import static org.hamcrest.MatcherAssert.assertThat;
import java.util.Arrays;
import java.util.Collection;
+import java.util.function.Consumer;
import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jmeter.testelement.AbstractScopedTestElement;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;
@@ -235,6 +237,77 @@ public class TestJMESPathExtractor {
}
@RunWith(Parameterized.class)
+ public static class ScopedSamples {
+
+ enum AccessMode {
+ ALL(AbstractScopedTestElement::setScopeAll),
+ PARENT(AbstractScopedTestElement::setScopeParent),
+ CHILDREN(AbstractScopedTestElement::setScopeChildren);
+
+ private Consumer<AbstractScopedTestElement> applier;
+
+ AccessMode(Consumer<AbstractScopedTestElement> applier) {
+ this.applier = applier;
+ }
+
+ void configure(AbstractScopedTestElement element) {
+ applier.accept(element);
+ }
+ }
+
+ private AccessMode accessMode;
+ private String resultObject;
+ private String resultCount;
+ private String matchNumber;
+ private String path;
+
+ public ScopedSamples(AccessMode accessMode, String path, String
matchNumber, String result, String resultCount) {
+ this.accessMode = accessMode;
+ this.path = path;
+ this.matchNumber = matchNumber;
+ this.resultObject = result;
+ this.resultCount = resultCount;
+ }
+
+ @Parameters(name = "{index}: Mode: {0} Path: {1} MatchNr: {2} Result:
{3} Count: {4}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(
+ new Object[][] {
+ { AccessMode.ALL, "a", "1", "23", "2" },
+ { AccessMode.ALL, "a", "2", "42", "2" },
+ { AccessMode.ALL, "b", "0", "parent_only", "1" },
+ { AccessMode.ALL, "c", "0", "child_only", "1" },
+ { AccessMode.PARENT, "a", "1", "23", "1" },
+ { AccessMode.PARENT, "b", "0", "parent_only", "1" },
+ { AccessMode.PARENT, "c", "0", "NONE", "0" },
+ { AccessMode.CHILDREN, "a", "1", "42", "1" },
+ { AccessMode.CHILDREN, "b", "0", "NONE", "0" },
+ { AccessMode.CHILDREN, "c", "0", "child_only", "1" },
+ }
+ );
+ }
+
+ @Test
+ public void testRandomElementAllMatches() {
+ SampleResult sampleResult = new SampleResult();
+ JMeterVariables vars = new JMeterVariables();
+ JMESPathExtractor processor = setupProcessor(vars, sampleResult,
"{\"a\": 23, \"b\": \"parent_only\"}", false, matchNumber);
+ SampleResult subSample = new SampleResult();
+ subSample.setResponseData("{\"a\": 42, \"c\": \"child_only\"}",
null);
+ sampleResult.addSubResult(subSample);
+
+ processor.setJmesPathExpression(path);
+ accessMode.configure(processor);
+
+ processor.process();
+ assertThat(vars.get(REFERENCE_NAME),
CoreMatchers.is(resultObject));
+ assertThat(vars.get(REFERENCE_NAME + "_1"),
CoreMatchers.is(CoreMatchers.nullValue()));
+ assertThat(vars.get(REFERENCE_NAME_MATCH_NUMBER),
CoreMatchers.is(resultCount));
+ }
+
+ }
+
+ @RunWith(Parameterized.class)
public static class SourceVarOrResponse {
private boolean fromVariables;
diff --git a/xdocs/changes.xml b/xdocs/changes.xml
index 845f5e9..be5d038 100644
--- a/xdocs/changes.xml
+++ b/xdocs/changes.xml
@@ -168,6 +168,7 @@ Summary
<ul>
<li><bug>65257</bug>JMESPathExtractor writes error log entries if JMESPath
filter returns empty result</li>
<li><bug>65259</bug>JMESPathExtractor Attribute <code>Match No.</code>
Required</li>
+ <li><bug>65269</bug>JSON Extractor and JSON JMESPath Extractor ignore
sub-samples</li>
</ul>
<h3>Functions</h3>
diff --git a/xdocs/usermanual/component_reference.xml
b/xdocs/usermanual/component_reference.xml
index be770b4..c3e08b7 100644
--- a/xdocs/usermanual/component_reference.xml
+++ b/xdocs/usermanual/component_reference.xml
@@ -6327,15 +6327,26 @@ It will allow you to extract in a very easy way text
content, see <a href="https
</description>
<properties>
<property name="Name" required="No">Descriptive name for this element that
is shown in the tree.</property>
- <property name="Names of created variables" required="Yes">Semi-colon
separated names of variables that will contain the results of JSON-PATH
expressions (must match number of JSON-PATH expressions)</property>
- <property name="JSON Path Expressions" required="Yes">Semi-colon separated
JSON-PATH expressions (must match number of variables)</property>
- <property name="Default Values" required="No">Semi-colon separated default
values if JSON-PATH expressions do not return any result(must match number of
variables)</property>
- <property name="Match No. (0 for Random)" required="No">If the JSON Path
query leads to many results, you can choose which one(s) to extract as
Variables:
+ <property name="Apply to:" required="Yes">
+ This is for use with samplers that can generate sub-samples,
+ e.g. HTTP Sampler with embedded resources, Mail Reader or samples
generated by the Transaction Controller.
+ <dl>
+ <dt><code>Main sample only</code></dt><dd>only applies to the main
sample</dd>
+ <dt><code>Sub-samples only</code></dt><dd>only applies to the
sub-samples</dd>
+ <dt><code>Main sample and sub-samples</code></dt><dd>applies to
both.</dd>
+ <dt><code>JMeter Variable Name to use</code></dt><dd>extraction is to
be applied to the contents of the named variable</dd>
+ </dl>
+ </property>
+ <property name="Names of created variables" required="Yes">Semicolon
separated names of variables that will contain the results of JSON-PATH
expressions (must match number of JSON-PATH expressions)</property>
+ <property name="JSON Path Expressions" required="Yes">Semicolon separated
JSON-PATH expressions (must match number of variables)</property>
+ <property name="Default Values" required="No">Semicolon separated default
values if JSON-PATH expressions do not return any result(must match number of
variables)</property>
+ <property name="Match Numbers" required="No">For each JSON Path
Expression, if the JSON Path query leads to many results, you can choose which
one(s) to extract as Variables:
<ul>
<li><code>0</code>: means random (Default Value)</li>
<li><code>-1</code> means extract all results, they will be named as
<code><em><variable name></em>_N</code> (where <code>N</code> goes from 1
to Number of results)</li>
- <li><code>X</code>: means extract the X<sup>th</sup> result. If this
X<sup>th</sup> is greater than number of matches, then nothing is returned.
Default value will be used</li>
+ <li><code>X</code>: means extract the <em>X</em><sup>th</sup> result.
If this <em>X</em><sup>th</sup> is greater than number of matches, then nothing
is returned. Default value will be used</li>
</ul>
+ The numbers have to be given as a Semicolon separated list. The number of
elements in that list have to match the number of given JSON Path Expressions.
If left empty, the value <code>0</code> will be used as default for every
expression.
</property>
<property name="Compute concatenation var" required="No">If many results
are found, plugin will concatenate them using ‘<code>,</code>’ separator and
store it in a var named <code><em><variable
name></em>_ALL</code></property>
</properties>