diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java index 72a7f8fd..0b4a50c5 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java @@ -167,16 +167,29 @@ public class ScanPathToken extends PathToken { @Override public boolean matches(Object model) { - if (ctx.jsonProvider().isMap(model)) { - if (ctx.options().contains(Option.REQUIRE_PROPERTIES)) { - // Have to require properties defined in path when an indefinite path is evaluated, - // so have to go there and search for it. - return true; - } - Collection keys = ctx.jsonProvider().getPropertyKeys(model); - return keys.containsAll(propertyPathToken.getProperties()); + if (! ctx.jsonProvider().isMap(model)) { + return false; } - return false; + + if (ctx.options().contains(Option.REQUIRE_PROPERTIES)) { + // Have to require properties defined in path when an indefinite path is evaluated, + // so have to go there and search for it. + return true; + } + + if (! propertyPathToken.isTokenDefinite()) { + // It's responsibility of PropertyPathToken code to handle indefinite scenario of properties, + // so we'll allow it to do its job. + return true; + } + + if (ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)) { + // In case of DEFAULT_PATH_LEAF_TO_NULL missing properties is not a problem. + return true; + } + + Collection keys = ctx.jsonProvider().getPropertyKeys(model); + return keys.containsAll(propertyPathToken.getProperties()); } } } 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 e85566d2..6724809e 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/DeepScanTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/DeepScanTest.java @@ -2,10 +2,14 @@ package com.jayway.jsonpath; import org.junit.Test; +import static com.jayway.jsonpath.JsonPath.using; import static com.jayway.jsonpath.TestUtils.assertEvaluationThrows; import static org.assertj.core.api.Assertions.assertThat; +import java.util.List; +import java.util.Map; + /** * Deep scan is indefinite, so certain "illegal" actions become a no-op instead of a path evaluation exception. */ @@ -84,5 +88,31 @@ public class DeepScanTest extends BaseTest { // foo.bar must be found in every object node after deep scan (which is impossible) assertEvaluationThrows("{\"foo\": {\"bar\": 4}}", "$..foo.bar", PathNotFoundException.class, conf); + + assertEvaluationThrows("{\"foo\": {\"bar\": 4}, \"baz\": 2}", "$..['foo', 'baz']", PathNotFoundException.class, conf); + } + + @Test + public void when_deep_scanning_leaf_multi_props_work() { + Object result = JsonPath.parse("[{\"a\": \"a-val\", \"b\": \"b-val\", \"c\": \"c-val\"}, [1, 5], {\"a\": \"a-val\"}]").read( + "$..['a', 'c']"); + // This is current deep scan semantics: only objects containing all properties specified in multiprops token are + // considered. + assertThat(result).asList().hasSize(1); + result = ((List)result).get(0); + + assertThat(result).isInstanceOf(Map.class); + assertThat((Map)result).hasSize(2).containsEntry("a", "a-val").containsEntry("c", "c-val"); + + // But this semantics changes when DEFAULT_PATH_LEAF_TO_NULL comes into play. + Configuration conf = Configuration.defaultConfiguration().addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL); + result = using(conf).parse("[{\"a\": \"a-val\", \"b\": \"b-val\", \"c\": \"c-val\"}, [1, 5], {\"a\": \"a-val\"}]").read( + "$..['a', 'c']"); + // todo: deep equality test, but not tied to any json provider + assertThat(result).asList().hasSize(2); + for (final Object node : (List)result) { + assertThat(node).isInstanceOf(Map.class); + assertThat((Map)node).hasSize(2).containsEntry("a", "a-val"); + } } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java b/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java index c850eed3..aeb0bffb 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java @@ -24,6 +24,10 @@ public class MultiPropTest { assertThat(using(conf).parse(model).read("$['a', 'b']", Map.class)) .containsEntry("a", "a-val") .containsEntry("b", "b-val"); + + // current semantics: absent props are skipped + assertThat(using(conf).parse(model).read("$['a', 'd']", Map.class)) + .hasSize(1).containsEntry("a", "a-val"); } @Test