From 89df4455500177d07139cdb0cc37593d8a69f200 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Wed, 9 Sep 2026 10:14:28 +0100 Subject: [PATCH 1/2] (jakarta-jsonp) Port `JsonPatch` START_ARRAY check from jsr-353 module Commit 59447f3 ("Fixes wrt [core#1378]") added a START_ARRAY guard plus two tests to the jsr-353 `JsonPatchDeserializer`, but the jakarta-jsonp copy was never updated. Without the guard `_deserializeArray()` reads past the end of the document and switches on the resulting `null` token, so non-Array input fails with a bare NPE instead of `InvalidFormatException`: input jsr-353 jakarta-jsonp "op" InvalidFormatException NullPointerException: ... "t" is null 42 InvalidFormatException NullPointerException Port the guard verbatim and bring over the two tests (plus one for numbers). Also add the `a2q()` helper to `TestBase` for the new test content. Co-Authored-By: Claude Opus 5 (1M context) --- .../datatype/jsonp/JsonPatchDeserializer.java | 9 ++++++ .../jsonp/JsonPatchDeserializationTest.java | 28 +++++++++++++++++++ .../jackson/datatype/jsonp/TestBase.java | 4 +++ 3 files changed, 41 insertions(+) diff --git a/jakarta-jsonp/src/main/java/tools/jackson/datatype/jsonp/JsonPatchDeserializer.java b/jakarta-jsonp/src/main/java/tools/jackson/datatype/jsonp/JsonPatchDeserializer.java index 3f7ed48f..7bdc267d 100644 --- a/jakarta-jsonp/src/main/java/tools/jackson/datatype/jsonp/JsonPatchDeserializer.java +++ b/jakarta-jsonp/src/main/java/tools/jackson/datatype/jsonp/JsonPatchDeserializer.java @@ -2,8 +2,10 @@ import tools.jackson.core.JacksonException; import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.deser.std.StdDeserializer; +import tools.jackson.databind.exc.InvalidFormatException; import tools.jackson.databind.type.LogicalType; import jakarta.json.JsonPatch; @@ -27,6 +29,13 @@ public LogicalType logicalType() { public JsonPatch deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException { + // 09-Sep-2026, pjfanning: [datatypes-misc#92] Verify it IS an Array; otherwise + // `_deserializeArray()` reads past the end of the document and fails with + // a bare NPE on the resulting `null` token + if (p.currentToken() != JsonToken.START_ARRAY) { + throw InvalidFormatException.from(p, "JSON patch has to be an array of objects", p.getString(), + handledType()); + } return provider.createPatch(jsonValueDeser._deserializeArray(p, ctxt)); } diff --git a/jakarta-jsonp/src/test/java/tools/jackson/datatype/jsonp/JsonPatchDeserializationTest.java b/jakarta-jsonp/src/test/java/tools/jackson/datatype/jsonp/JsonPatchDeserializationTest.java index 2d35df26..0d55346c 100644 --- a/jakarta-jsonp/src/test/java/tools/jackson/datatype/jsonp/JsonPatchDeserializationTest.java +++ b/jakarta-jsonp/src/test/java/tools/jackson/datatype/jsonp/JsonPatchDeserializationTest.java @@ -1,6 +1,7 @@ package tools.jackson.datatype.jsonp; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.exc.InvalidFormatException; import jakarta.json.*; @@ -15,6 +16,8 @@ public class JsonPatchDeserializationTest extends TestBase { private static final ObjectMapper MAPPER = newMapper(); + private static final String EXPECTED_MESSAGE = "JSON patch has to be an array of objects"; + @Test public void testDeserializationAndPatching() throws Exception { final String json = "[" + @@ -51,6 +54,31 @@ public void testDeserializationAndPatching() throws Exception { assertThat(patchedPerson).isEqualTo(new Person("Json", "Smith")); } + @Test + public void testObjectDeserializationAndPatching() { + final String json = a2q("{'op':'replace','path':'/name','value':'Json'}"); + + final InvalidFormatException ex = assertThrows(InvalidFormatException.class, + () -> MAPPER.readValue(json, JsonPatch.class)); + assertThat(ex.getMessage()).contains(EXPECTED_MESSAGE); + } + + @Test + public void testScalarDeserializationAndPatching() { + final String json = a2q("'op'"); + + final InvalidFormatException ex = assertThrows(InvalidFormatException.class, + () -> MAPPER.readValue(json, JsonPatch.class)); + assertThat(ex.getMessage()).contains(EXPECTED_MESSAGE); + } + + @Test + public void testNumberDeserializationAndPatching() { + final InvalidFormatException ex = assertThrows(InvalidFormatException.class, + () -> MAPPER.readValue("42", JsonPatch.class)); + assertThat(ex.getMessage()).contains(EXPECTED_MESSAGE); + } + static class Person { private String name; private String lastName; diff --git a/jakarta-jsonp/src/test/java/tools/jackson/datatype/jsonp/TestBase.java b/jakarta-jsonp/src/test/java/tools/jackson/datatype/jsonp/TestBase.java index 5bfe6d26..d812cd6e 100644 --- a/jakarta-jsonp/src/test/java/tools/jackson/datatype/jsonp/TestBase.java +++ b/jakarta-jsonp/src/test/java/tools/jackson/datatype/jsonp/TestBase.java @@ -49,4 +49,8 @@ protected JsonArrayBuilder arrayBuilder() { protected JsonObjectBuilder objectBuilder() { return MODULE._builderFactory.createObjectBuilder(); } + + protected static String a2q(String json) { + return json.replace("'", "\""); + } } From e2118b52e44154766793ae128c4fb365697ab491 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Wed, 9 Sep 2026 10:14:42 +0100 Subject: [PATCH 2/2] Add release notes entry for #92 Co-Authored-By: Claude Opus 5 (1M context) --- release-notes/VERSION | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index 6c9ef325..b6dcd6d9 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -15,7 +15,8 @@ Modules: 3.3.0 (not yet released) -No changes since 3.2 +#92: (jakarta-jsonp) `JsonPatch` deserialization throws `NullPointerException` + for non-Array input 3.2.2 (14-Aug-2026)