From 7370126b5fa9539a48ca1ca666f8038d25639062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=98=E5=85=B8?= Date: Sun, 13 Sep 2026 01:51:58 +0800 Subject: [PATCH] Fix deep scan results dropped after wildcard or multi index tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A deep scan iterating an array feeds every element to the token that follows and records the element index in the leaf token. The leaf filter introduced with 2.7.0 accepted a result only when the path fragment of the token before the leaf literally equaled "[]". That comparison can never succeed for wildcard tokens ("[*]"), so paths like $..*.title returned an empty list since 2.7.0, and multi index tokens like $..[0,2] were dropped the same way. Replace the string comparison with acceptsUpstreamArrayIndex, which only explicit array index tokens restrict by their actual indexes. Wildcard, slice, scan, property and predicate tokens accept every element, so $..*.title returns all four book titles again while $..[2].author still returns only the third author. A token without a previous link is mounted by a function parameter evaluation (see Length) and has no path constraint to apply, so it accepts every element instead of failing. Fixes #1052 Signed-off-by: 付典 --- .../internal/path/ArrayIndexToken.java | 5 ++ .../jsonpath/internal/path/PathToken.java | 17 +++++- .../com/jayway/jsonpath/DeepScanTest.java | 60 +++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) 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() {