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 3f7ed48..7bdc267 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 2d35df2..0d55346 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 5bfe6d2..d812cd6 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("'", "\""); + } } diff --git a/release-notes/VERSION b/release-notes/VERSION index 6c9ef32..b6dcd6d 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)