diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java index 3e4b36e2e..32a4aa605 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java @@ -44,6 +44,11 @@ public String getPathFragment() { return arrayIndexOperation.toString(); } + @Override + public boolean acceptsUpstreamArrayIndex(int index) { + return arrayIndexOperation.indexes().contains(index); + } + @Override public boolean isTokenDefinite() { return arrayIndexOperation.isSingleIndexOperation(); 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 34cf1402b..a054fa542 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 @@ -36,6 +36,17 @@ public void setUpstreamArrayIndex(int idx){ upstreamArrayIndex = idx; } + /** + * Whether an array element at the given index, reached by a deep scan + * iterating an array, satisfies this token's own selection so that its + * leaf properties may contribute to the result. Only explicit array + * index tokens restrict the selection; wildcard, slice, scan, property + * and predicate tokens accept every element. + */ + public boolean acceptsUpstreamArrayIndex(int index) { + return true; + } + PathToken appendTailToken(PathToken next) { this.next = next; this.next.prev = this; @@ -81,8 +92,10 @@ void handleObjectProperty(String currentPath, Object model, EvaluationContextImp } PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, property) : PathRef.NO_OP; if (isLeaf()) { - String idx = "[" + String.valueOf(upstreamArrayIndex) + "]"; - if(idx.equals("[-1]") || ctx.getRoot().getTail().prev().getPathFragment().equals(idx)){ + // A token without a previous link is mounted by a function parameter + // evaluation (see Length), so there is no path constraint to apply + if (upstreamArrayIndex == -1 || prev() == null + || prev().acceptsUpstreamArrayIndex(upstreamArrayIndex)) { ctx.addResult(evalPath, pathRef, propertyVal); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/DeepScanTest.java b/json-path/src/test/java/com/jayway/jsonpath/DeepScanTest.java index 3730c01df..6391042c8 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/DeepScanTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/DeepScanTest.java @@ -127,6 +127,66 @@ public void when_deep_scanning_leaf_multi_props_work() { } } + @Test + public void when_deep_scanning_wildcard_before_leaf_property_works() { + Object result = JsonPath.parse(JSON_DOCUMENT).read("$..*.title"); + assertThat(result).asList().containsExactly( + "Sayings of the Century", "Sword of Honour", "Moby Dick", "The Lord of the Rings"); + } + + @Test + public void when_deep_scanning_single_array_index_before_leaf_property_works() { + Object result = JsonPath.parse(JSON_DOCUMENT).read("$..[2].author"); + assertThat(result).asList().containsExactly("Herman Melville"); + } + + @Test + public void when_deep_scanning_multiple_array_indexes_before_leaf_property_works() { + Object result = JsonPath.parse(JSON_DOCUMENT).read("$..[0,2].author"); + assertThat(result).asList().containsExactly("Nigel Rees", "Herman Melville"); + } + + @Test + public void when_deep_scanning_function_parameter_with_scan_wildcard_does_not_throw() { + // $..* feeds Length through a wildcard that is mounted on the parameter + // path without a previous link; the leaf filter must not dereference it + // the function mounts its wildcard on the parameter path without a + // previous link, so the leaf filter must not dereference it; the + // values match the completed deep scan results after this fix + Object result = JsonPath.parse(JSON_DOCUMENT).read("$..*.length()"); + assertThat(result).isEqualTo(31); + + result = JsonPath.parse(JSON_DOCUMENT).read("$..[0].length()"); + assertThat(result).isEqualTo(18); + + result = JsonPath.parse(JSON_DOCUMENT).read("$..[*].length()"); + assertThat(result).isEqualTo(31); + } + + @Test + public void when_deep_scanning_double_wildcard_after_fix() { + // the same fix family: a wildcard fed by a deep scan used to drop + // every result, so the second wildcard received nothing + Object result = JsonPath.parse(JSON_DOCUMENT).read("$..*.*"); + assertThat(result).asList().hasSize(31); + } + + @Test + public void when_deep_scanning_slice_before_leaf_property_accepts_all_elements() { + // documented boundary: a slice upstream accepts every fed element, the + // exact [from,to) range would need the array length at the leaf + Object result = JsonPath.parse(JSON_DOCUMENT).read("$..[1:3].author"); + assertThat(result).asList().containsExactly( + "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"); + } + + @Test + public void when_deep_scanning_negative_index_before_leaf_property_returns_empty() { + // pre-existing: negative indexes never match the fed element indexes + Object result = JsonPath.parse(JSON_DOCUMENT).read("$..[-1].author"); + assertThat(result).asList().isEmpty(); + } + @Test public void require_single_property_ok() {