diff --git a/json-path/src/main/java/com/jayway/jsonpath/Option.java b/json-path/src/main/java/com/jayway/jsonpath/Option.java index 412703fd..3b261bcb 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Option.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Option.java @@ -83,6 +83,47 @@ public enum Option { * If REQUIRE_PROPERTIES option is present PathNotFoundException is thrown. * If REQUIRE_PROPERTIES option is not present ["b-val"] is returned. */ - REQUIRE_PROPERTIES + REQUIRE_PROPERTIES, + + /** + * This mode can be used query at special filters. + * In the origin design, the filter receive an array and return an array. + * However, the subsequent operation will apply on each element in the returned array, rather than the whole array itself. + * The mode treats the result of filter as array, and it might be useful to some situation. + * For example: + * + * Given: + *
+ * [ + * { + * "price" : 20, + * "name" : "book1" + * }, + * { + * "price" : 30, + * "name" : "book1" + * }, + * { + * "price" : 40, + * "name" : "book1" + * }, + * { + * "price" : 50, + * "name" : "book1" + * }, + * ] + *+ * + * We want to get the first book which price is greater than 25. + * + * evaluating the path "$[?(@.price > 25)][0]" + * + * when using classic mode, the result will be null. + * when using this mode, the result will be [{"price":30,"name":"book1"}]. That is what we expected. + * + * + * Notice: When using this mode, the path of the result will be incorrect. Besides, SET operation will don't work. + */ + FILTER_AS_ARRAY } 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 34cf1402..70960725 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 @@ -146,6 +146,20 @@ public abstract class PathToken { } } + protected void handleWholeArray(String currentPath, Object model, EvaluationContextImpl ctx){ + // using FILTER_AS_ARRAY mode, details at com/jayway/jsonpath/Option.FILTER_AS_ARRAY + // NOTICE: When using this mode, the path of the result will be incorrect. Besides, SET operation will don't work. + if(isLeaf()){ + Iterable> it = ctx.jsonProvider().toIterable(model); + for(Object object : it){ + ctx.addResult(currentPath, PathRef.NO_OP, object); // Use PathRef.NO_OP because the SET operation is banned. + } + } + else{ + next().evaluate(currentPath, PathRef.NO_OP, model, ctx); + } + } + PathToken prev(){ return prev; } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java index ee67fc1e..d6855e49 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java @@ -16,11 +16,13 @@ package com.jayway.jsonpath.internal.path; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.InvalidPathException; +import com.jayway.jsonpath.Option; import com.jayway.jsonpath.Predicate; import com.jayway.jsonpath.internal.PathRef; import java.util.Collection; import java.util.Collections; +import java.util.List; import static java.lang.String.format; import static java.util.Arrays.asList; @@ -53,14 +55,26 @@ public class PredicatePathToken extends PathToken { } } } else if (ctx.jsonProvider().isArray(model)){ - int idx = 0; - Iterable> objects = ctx.jsonProvider().toIterable(model); - - for (Object idxModel : objects) { - if (accept(idxModel, ctx.rootDocument(), ctx.configuration(), ctx)) { - handleArrayIndex(idx, currentPath, model, ctx); + if (ctx.configuration().containsOption(Option.FILTER_AS_ARRAY)){ + //using FILTER_AS_ARRAY mode, details at com/jayway/jsonpath/Option.FILTER_AS_ARRAY + Iterable> objects = ctx.jsonProvider().toIterable(model); + Object filteredModel = ctx.jsonProvider().createArray(); + for(Object idxModel : objects){ + if(accept(idxModel, ctx.rootDocument(), ctx.configuration(), ctx)){ + ((List)filteredModel).add(idxModel); + } + } + handleWholeArray(currentPath, filteredModel, ctx); + } else { + int idx = 0; + Iterable> objects = ctx.jsonProvider().toIterable(model); + + for (Object idxModel : objects) { + if (accept(idxModel, ctx.rootDocument(), ctx.configuration(), ctx)) { + handleArrayIndex(idx, currentPath, model, ctx); + } + idx++; } - idx++; } } else { if (isUpstreamDefinite()) { diff --git a/json-path/src/test/java/com/jayway/jsonpath/Issue_654.java b/json-path/src/test/java/com/jayway/jsonpath/Issue_654.java new file mode 100644 index 00000000..0cb449a6 --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/Issue_654.java @@ -0,0 +1,38 @@ +package com.jayway.jsonpath; + + +import org.junit.Test; + +public class Issue_654 { + + public static final Configuration conf = Configuration.builder().options(Option.FILTER_AS_ARRAY).build(); + + public static final String json = "[\n" + + " [0, 1, 2], \n" + + " [3, 4, 5],\n" + + " [6, 7, 8],\n" + + " [9, 10, 11],\n" + + " [12, 13, 14]\n" + + "]"; + + + @Test + public void test_1(){ + // find the first array that its first element is greater than 4 + Object res_ori = JsonPath.parse(json).read("$[?(@[0] > 4)][0]"); + Object res_new = JsonPath.using(conf).parse(json).read("$[?(@[0] > 4)][0]"); + assert (res_ori.toString().equals("[6,9,12]")); // origin mode will use INDEX_AT(0) operation on [6, 7, 8], [9, 10, 11], [12, 13, 14] respectively; + assert (res_new.toString().equals("[[6,7,8]]")); // new mode use INDEX_AT(0) operation on ([6, 7, 8], [9, 10, 11], [12, 13, 14]) + } + + @Test + public void test_2(){ + // find the count of the elements whose first element is greater than 4 + Object res_ori = JsonPath.parse(json).read("$[?(@[0] > 4)].length()"); + Object res_new = JsonPath.using(conf).parse(json).read("$[?(@[0] > 4)].length()"); + assert (res_ori.toString().equals("[3,3,3]")); // origin mode will use length() function on [6, 7, 8], [9, 10, 11], [12, 13, 14] respectively; + assert (res_new.toString().equals("[3]")); // new mode use length() function on ([6, 7, 8], [9, 10, 11], [12, 13, 14]) + } + + +} \ No newline at end of file