diff --git a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java index 01c7db03..8c4308f2 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.ArrayList; import static com.jayway.jsonpath.Option.ALWAYS_RETURN_LIST; import static com.jayway.jsonpath.Option.AS_PATH_LIST; @@ -749,7 +750,13 @@ private T handleMissingPathInContext(final Configuration configuration) { boolean optAsPathList = configuration.containsOption(AS_PATH_LIST); boolean optAlwaysReturnList = configuration.containsOption(Option.ALWAYS_RETURN_LIST); if (optAsPathList) { - return (T) configuration.jsonProvider().createArray(); + // AS_PATH_LIST results are java.util.List path lists (see + // resultByConfiguration / EvaluationContextImpl.getPathList), consumed by the + // write operations of JsonContext which cast them to List. Returning + // jsonProvider.createArray() here broke that contract with document-model + // providers whose arrays are not java.util.List (e.g. Jackson's ArrayNode), + // throwing ClassCastException on non-matching paths with SUPPRESS_EXCEPTIONS. + return (T) new ArrayList(); } else { if (optAlwaysReturnList) { return (T) configuration.jsonProvider().createArray(); diff --git a/json-path/src/test/java/com/jayway/jsonpath/Jackson3JsonNodeJsonProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/Jackson3JsonNodeJsonProviderTest.java index 06b595a5..27c9a28d 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/Jackson3JsonNodeJsonProviderTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/Jackson3JsonNodeJsonProviderTest.java @@ -281,4 +281,21 @@ public static final class Data { } } + + // Write operations on a non-matching path used to throw ClassCastException with + // SUPPRESS_EXCEPTIONS: handleMissingPathInContext returned jsonProvider.createArray() + // (an ArrayNode with this provider) where JsonContext casts the AS_PATH_LIST result + // to java.util.List. Root cause of zalando/logbook#1369. + @Test + public void set_on_non_matching_path_with_suppressed_exceptions_does_not_throw() { + Configuration conf = JACKSON3_JSON_NODE_CONFIGURATION.addOptions(Option.SUPPRESS_EXCEPTIONS); + DocumentContext context = using(conf).parse(JSON_DOCUMENT); + + context.set("$..does-not-exist.value", "XXX"); + context.delete("$..does-not-exist"); + context.renameKey("$..does-not-exist", "old", "new"); + + assertThat(context.read("$.string-property", String.class)).isEqualTo("string-value"); + } + } diff --git a/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderTest.java index 1684248b..056a464e 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderTest.java @@ -279,4 +279,21 @@ public static final class Data { } } + + // Write operations on a non-matching path used to throw ClassCastException with + // SUPPRESS_EXCEPTIONS: handleMissingPathInContext returned jsonProvider.createArray() + // (an ArrayNode with this provider) where JsonContext casts the AS_PATH_LIST result + // to java.util.List. Root cause of zalando/logbook#1369. + @Test + public void set_on_non_matching_path_with_suppressed_exceptions_does_not_throw() { + Configuration conf = JACKSON_JSON_NODE_CONFIGURATION.addOptions(Option.SUPPRESS_EXCEPTIONS); + DocumentContext context = using(conf).parse(JSON_DOCUMENT); + + context.set("$..does-not-exist.value", "XXX"); + context.delete("$..does-not-exist"); + context.renameKey("$..does-not-exist", "old", "new"); + + assertThat(context.read("$.string-property", String.class)).isEqualTo("string-value"); + } + }