From c82f6eaa547241dcccedc7f20b9d43e2fd14e363 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 5 May 2026 13:26:51 -0700 Subject: [PATCH 1/6] Improve `java.time.Month` deserialization validation --- .../ext/javatime/deser/MonthDeserializer.java | 21 ++++++- .../javatime/deser/MonthDeserializerTest.java | 61 ++++++++++++++----- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java index 75b8dd454b..51dc667c8c 100644 --- a/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java +++ b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java @@ -9,6 +9,7 @@ import tools.jackson.core.*; import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.cfg.CoercionAction; import tools.jackson.databind.cfg.DateTimeFeature; import com.fasterxml.jackson.annotation.JsonFormat; @@ -79,12 +80,26 @@ public Month deserialize(JsonParser p, DeserializationContext ctxt) } // fall through } else if (p.isExpectedStartArrayToken()) { + // [JDB-040]: Respect coercion settings; do not accept empty or single-element + // arrays unless the corresponding DeserializationFeature is enabled. JsonToken t = p.nextToken(); if (t == JsonToken.END_ARRAY) { - return null; + final CoercionAction act = _findCoercionFromEmptyArray(ctxt); + if (act == CoercionAction.AsNull || act == CoercionAction.TryConvert) { + return null; + } + if (act == CoercionAction.AsEmpty) { + return (Month) getEmptyValue(ctxt); + } + return (Month) ctxt.handleUnexpectedToken(getValueType(ctxt), + JsonToken.START_ARRAY, p, "Cannot deserialize `Month` from empty JSON Array"); + } + if (!ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + return (Month) ctxt.handleUnexpectedToken(getValueType(ctxt), + JsonToken.START_ARRAY, p, + "Cannot deserialize `Month` out of START_ARRAY token: enable `DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS`"); } - if ((t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT) - && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + if ((t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT)) { final Month parsed = deserialize(p, ctxt); if (p.nextToken() != JsonToken.END_ARRAY) { handleMissingEndArrayForSingle(p, ctxt); diff --git a/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java b/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java index 3db218abe1..b37dc3593f 100644 --- a/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java +++ b/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java @@ -261,78 +261,107 @@ public void testDeserializationAsIntOutOfRange_oneBased(int invalidValue) throws @Test public void testDeserializationAsEmptyArray() throws Exception { - // Empty array returns null - Month result = readerForOneBased().readValue("[]"); + // [JDB-040]: Empty array now requires ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT to return null + assertError( + () -> readerForOneBased().readValue("[]"), + MismatchedInputException.class, + "Cannot deserialize" + ); + } + + @Test + public void testDeserializationAsEmptyArray_withFeatureEnabled() throws Exception + { + ObjectMapper mapper = newMapper().rebuild() + .enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT).build(); + Month result = mapper.readerFor(Month.class).readValue("[]"); assertNull(result); } @Test public void testDeserializationAsArrayWithIntValue() throws Exception { - // Array with single int value (interpreted as 1-based month) - Month result = readerForOneBased().readValue("[3]"); + // [JDB-040]: Single-element int array now requires UNWRAP_SINGLE_VALUE_ARRAYS + assertError( + () -> readerForOneBased().readValue("[3]"), + MismatchedInputException.class, + "UNWRAP_SINGLE_VALUE_ARRAYS" + ); + } + + @Test + public void testDeserializationAsArrayWithIntValue_withFeatureEnabled() throws Exception + { + ObjectMapper mapper = newMapper().rebuild() + .enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS).build(); + Month result = mapper.readerFor(Month.class).readValue("[3]"); assertEquals(Month.MARCH, result); } @Test public void testDeserializationAsArrayWithIntValue_zeroBased() throws Exception { - // Array with single int value (0-based mode still uses Month.of for array) - Month result = readerForZeroBased().readValue("[3]"); - assertEquals(Month.MARCH, result); + // [JDB-040]: Single-element int array now requires UNWRAP_SINGLE_VALUE_ARRAYS + assertError( + () -> readerForZeroBased().readValue("[3]"), + MismatchedInputException.class, + "UNWRAP_SINGLE_VALUE_ARRAYS" + ); } @Test public void testDeserializationAsArrayWithMoreThanOneElement() throws Exception { + // [JDB-040]: Multi-element arrays fail with UNWRAP_SINGLE_VALUE_ARRAYS message + // (since UNWRAP check fires before individual token checks) assertError( () -> readerForOneBased().readValue("[1, 2]"), MismatchedInputException.class, - "Expected array to end" + "UNWRAP_SINGLE_VALUE_ARRAYS" ); } @Test public void testDeserializationAsArrayWithWrongToken() throws Exception { - // Boolean in array without UNWRAP should fail with specific error + // [JDB-040]: Boolean in array without UNWRAP should fail (UNWRAP check fires first) assertError( () -> readerForOneBased().readValue("[true]"), MismatchedInputException.class, - "Expected VALUE_NUMBER_INT" + "UNWRAP_SINGLE_VALUE_ARRAYS" ); } @Test public void testDeserializationAsArrayWithStringUnwrapDisabled() throws Exception { - // String in array without UNWRAP_SINGLE_VALUE_ARRAYS should fail + // [JDB-040]: String in array without UNWRAP_SINGLE_VALUE_ARRAYS should fail assertError( () -> readerForOneBased().readValue("[\"JANUARY\"]"), MismatchedInputException.class, - "Expected VALUE_NUMBER_INT" + "UNWRAP_SINGLE_VALUE_ARRAYS" ); } @Test public void testDeserializationAsArrayWithFloatUnwrapDisabled() throws Exception { - // Float in array without UNWRAP should fail + // [JDB-040]: Float in array without UNWRAP should fail (UNWRAP check fires first) assertError( () -> readerForOneBased().readValue("[1.5]"), MismatchedInputException.class, - "Expected VALUE_NUMBER_INT" + "UNWRAP_SINGLE_VALUE_ARRAYS" ); } @Test public void testDeserializationAsArrayWithObjectUnwrapDisabled() throws Exception { - // Object in array without UNWRAP should fail + // [JDB-040]: Object in array without UNWRAP should fail (UNWRAP check fires first) assertError( () -> readerForOneBased().readValue("[{}]"), MismatchedInputException.class, - "Expected VALUE_NUMBER_INT" + "UNWRAP_SINGLE_VALUE_ARRAYS" ); } From a1f41a4425598efe9b0a5cbc5f83cbce9b375e56 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 5 May 2026 13:34:37 -0700 Subject: [PATCH 2/6] Add release notes, clean up --- release-notes/CREDITS | 2 ++ release-notes/VERSION | 2 ++ .../ext/javatime/deser/MonthDeserializer.java | 2 +- .../javatime/deser/MonthDeserializerTest.java | 22 +++++++++---------- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 210268c878..eef5c302ce 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -341,6 +341,8 @@ Omkhar Arasaratnam (@omkhar) [3.1.4] * Reported, suggested fix for #5956: Fix problem with float-to-byte range check [3.1.4] + * Contributed fix for #5957: Improve `java.time.Month` deserialization validation + [3.1.4] Michael Orzechowski (@MikeBlink) * Reported #5941: `MapperBuilder.addModule()` does not recursively register transitive diff --git a/release-notes/VERSION b/release-notes/VERSION index 8c5d7e8343..f052e5988a 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -15,6 +15,8 @@ No changes since 3.1 (reported, fix suggested by Omkhar A) #5956: Fix problem with float-to-byte range check (reported, fix suggested by Omkhar A) +#5957: Improve `java.time.Month` deserialization validation + (fix by Omkhar A) 3.1.3 (01-May-2026) diff --git a/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java index 51dc667c8c..dad3ea8657 100644 --- a/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java +++ b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java @@ -80,7 +80,7 @@ public Month deserialize(JsonParser p, DeserializationContext ctxt) } // fall through } else if (p.isExpectedStartArrayToken()) { - // [JDB-040]: Respect coercion settings; do not accept empty or single-element + // [databind#5957]: Respect coercion settings; do not accept empty or single-element // arrays unless the corresponding DeserializationFeature is enabled. JsonToken t = p.nextToken(); if (t == JsonToken.END_ARRAY) { diff --git a/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java b/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java index b37dc3593f..034f0a434d 100644 --- a/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java +++ b/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java @@ -261,7 +261,7 @@ public void testDeserializationAsIntOutOfRange_oneBased(int invalidValue) throws @Test public void testDeserializationAsEmptyArray() throws Exception { - // [JDB-040]: Empty array now requires ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT to return null + // [databind#5957]: Empty array now requires ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT to return null assertError( () -> readerForOneBased().readValue("[]"), MismatchedInputException.class, @@ -281,7 +281,7 @@ public void testDeserializationAsEmptyArray_withFeatureEnabled() throws Exceptio @Test public void testDeserializationAsArrayWithIntValue() throws Exception { - // [JDB-040]: Single-element int array now requires UNWRAP_SINGLE_VALUE_ARRAYS + // [databind#5957]: Single-element int array now requires UNWRAP_SINGLE_VALUE_ARRAYS assertError( () -> readerForOneBased().readValue("[3]"), MismatchedInputException.class, @@ -292,16 +292,16 @@ public void testDeserializationAsArrayWithIntValue() throws Exception @Test public void testDeserializationAsArrayWithIntValue_withFeatureEnabled() throws Exception { - ObjectMapper mapper = newMapper().rebuild() - .enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS).build(); - Month result = mapper.readerFor(Month.class).readValue("[3]"); + ObjectReader r = readerForOneBased() + .with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + Month result = r.readValue("[3]"); assertEquals(Month.MARCH, result); } @Test public void testDeserializationAsArrayWithIntValue_zeroBased() throws Exception { - // [JDB-040]: Single-element int array now requires UNWRAP_SINGLE_VALUE_ARRAYS + // [databind#5957]: Single-element int array now requires UNWRAP_SINGLE_VALUE_ARRAYS assertError( () -> readerForZeroBased().readValue("[3]"), MismatchedInputException.class, @@ -312,7 +312,7 @@ public void testDeserializationAsArrayWithIntValue_zeroBased() throws Exception @Test public void testDeserializationAsArrayWithMoreThanOneElement() throws Exception { - // [JDB-040]: Multi-element arrays fail with UNWRAP_SINGLE_VALUE_ARRAYS message + // [databind#5957]: Multi-element arrays fail with UNWRAP_SINGLE_VALUE_ARRAYS message // (since UNWRAP check fires before individual token checks) assertError( () -> readerForOneBased().readValue("[1, 2]"), @@ -324,7 +324,7 @@ public void testDeserializationAsArrayWithMoreThanOneElement() throws Exception @Test public void testDeserializationAsArrayWithWrongToken() throws Exception { - // [JDB-040]: Boolean in array without UNWRAP should fail (UNWRAP check fires first) + // [databind#5957]: Boolean in array without UNWRAP should fail (UNWRAP check fires first) assertError( () -> readerForOneBased().readValue("[true]"), MismatchedInputException.class, @@ -335,7 +335,7 @@ public void testDeserializationAsArrayWithWrongToken() throws Exception @Test public void testDeserializationAsArrayWithStringUnwrapDisabled() throws Exception { - // [JDB-040]: String in array without UNWRAP_SINGLE_VALUE_ARRAYS should fail + // [databind#5957]: String in array without UNWRAP_SINGLE_VALUE_ARRAYS should fail assertError( () -> readerForOneBased().readValue("[\"JANUARY\"]"), MismatchedInputException.class, @@ -346,7 +346,7 @@ public void testDeserializationAsArrayWithStringUnwrapDisabled() throws Exceptio @Test public void testDeserializationAsArrayWithFloatUnwrapDisabled() throws Exception { - // [JDB-040]: Float in array without UNWRAP should fail (UNWRAP check fires first) + // [databind#5957]: Float in array without UNWRAP should fail (UNWRAP check fires first) assertError( () -> readerForOneBased().readValue("[1.5]"), MismatchedInputException.class, @@ -357,7 +357,7 @@ public void testDeserializationAsArrayWithFloatUnwrapDisabled() throws Exception @Test public void testDeserializationAsArrayWithObjectUnwrapDisabled() throws Exception { - // [JDB-040]: Object in array without UNWRAP should fail (UNWRAP check fires first) + // [databind#5957]: Object in array without UNWRAP should fail (UNWRAP check fires first) assertError( () -> readerForOneBased().readValue("[{}]"), MismatchedInputException.class, From 9e8916816ca180e84e12342450e845b6197759e9 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 5 May 2026 13:43:43 -0700 Subject: [PATCH 3/6] Test, logic fixes. --- .../ext/javatime/deser/MonthDeserializer.java | 13 +++++++++---- .../ext/javatime/deser/MonthDeserializerTest.java | 12 ++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java index dad3ea8657..80fe4cd3ef 100644 --- a/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java +++ b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java @@ -94,21 +94,26 @@ public Month deserialize(JsonParser p, DeserializationContext ctxt) return (Month) ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, "Cannot deserialize `Month` from empty JSON Array"); } + // Validate token shape before checking UNWRAP, so inputs like [true], [1.5] + // or [{}] get a token-type diagnostic instead of a misleading suggestion to + // enable UNWRAP_SINGLE_VALUE_ARRAYS (which would not help). + if (t != JsonToken.VALUE_NUMBER_INT + && t != JsonToken.VALUE_STRING + && t != JsonToken.VALUE_EMBEDDED_OBJECT) { + return _reportWrongToken(ctxt, JsonToken.VALUE_NUMBER_INT, Integer.class.getName()); + } if (!ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { return (Month) ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, "Cannot deserialize `Month` out of START_ARRAY token: enable `DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS`"); } - if ((t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT)) { + if (t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT) { final Month parsed = deserialize(p, ctxt); if (p.nextToken() != JsonToken.END_ARRAY) { handleMissingEndArrayForSingle(p, ctxt); } return parsed; } - if (t != JsonToken.VALUE_NUMBER_INT) { - return _reportWrongToken(ctxt, JsonToken.VALUE_NUMBER_INT, Integer.class.getName()); - } int month = p.getIntValue(); if (p.nextToken() != JsonToken.END_ARRAY) { throw ctxt.wrongTokenException(p, handledType(), JsonToken.END_ARRAY, diff --git a/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java b/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java index 034f0a434d..7b40f78358 100644 --- a/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java +++ b/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java @@ -324,11 +324,11 @@ public void testDeserializationAsArrayWithMoreThanOneElement() throws Exception @Test public void testDeserializationAsArrayWithWrongToken() throws Exception { - // [databind#5957]: Boolean in array without UNWRAP should fail (UNWRAP check fires first) + // [databind#5957]: Boolean is not a coercible token shape; report token-type mismatch assertError( () -> readerForOneBased().readValue("[true]"), MismatchedInputException.class, - "UNWRAP_SINGLE_VALUE_ARRAYS" + "Expected VALUE_NUMBER_INT" ); } @@ -346,22 +346,22 @@ public void testDeserializationAsArrayWithStringUnwrapDisabled() throws Exceptio @Test public void testDeserializationAsArrayWithFloatUnwrapDisabled() throws Exception { - // [databind#5957]: Float in array without UNWRAP should fail (UNWRAP check fires first) + // [databind#5957]: Float is not a coercible token shape; report token-type mismatch assertError( () -> readerForOneBased().readValue("[1.5]"), MismatchedInputException.class, - "UNWRAP_SINGLE_VALUE_ARRAYS" + "Expected VALUE_NUMBER_INT" ); } @Test public void testDeserializationAsArrayWithObjectUnwrapDisabled() throws Exception { - // [databind#5957]: Object in array without UNWRAP should fail (UNWRAP check fires first) + // [databind#5957]: Object is not a coercible token shape; report token-type mismatch assertError( () -> readerForOneBased().readValue("[{}]"), MismatchedInputException.class, - "UNWRAP_SINGLE_VALUE_ARRAYS" + "Expected VALUE_NUMBER_INT" ); } From d11ad97adb28743605f753af322b6a414b8f870e Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 5 May 2026 13:50:42 -0700 Subject: [PATCH 4/6] ... --- .../jackson/databind/ext/javatime/deser/MonthDeserializer.java | 2 +- .../databind/ext/javatime/deser/MonthDeserializerTest.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java index 80fe4cd3ef..8afc61cb26 100644 --- a/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java +++ b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java @@ -100,7 +100,7 @@ public Month deserialize(JsonParser p, DeserializationContext ctxt) if (t != JsonToken.VALUE_NUMBER_INT && t != JsonToken.VALUE_STRING && t != JsonToken.VALUE_EMBEDDED_OBJECT) { - return _reportWrongToken(ctxt, JsonToken.VALUE_NUMBER_INT, Integer.class.getName()); + return _reportWrongToken(ctxt, JsonToken.VALUE_NUMBER_INT, "month"); } if (!ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { return (Month) ctxt.handleUnexpectedToken(getValueType(ctxt), diff --git a/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java b/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java index 7b40f78358..6ed3a7ea0f 100644 --- a/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java +++ b/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java @@ -313,7 +313,6 @@ public void testDeserializationAsArrayWithIntValue_zeroBased() throws Exception public void testDeserializationAsArrayWithMoreThanOneElement() throws Exception { // [databind#5957]: Multi-element arrays fail with UNWRAP_SINGLE_VALUE_ARRAYS message - // (since UNWRAP check fires before individual token checks) assertError( () -> readerForOneBased().readValue("[1, 2]"), MismatchedInputException.class, From fc83f2b38d28b58540aa95812514a97ccd705974 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 5 May 2026 13:52:58 -0700 Subject: [PATCH 5/6] Minor clean up --- .../jackson/databind/ext/javatime/deser/YearDeserializer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/tools/jackson/databind/ext/javatime/deser/YearDeserializer.java b/src/main/java/tools/jackson/databind/ext/javatime/deser/YearDeserializer.java index 2c7d77a1e3..a519dd0deb 100644 --- a/src/main/java/tools/jackson/databind/ext/javatime/deser/YearDeserializer.java +++ b/src/main/java/tools/jackson/databind/ext/javatime/deser/YearDeserializer.java @@ -91,14 +91,14 @@ public Year deserialize(JsonParser p, DeserializationContext ctxt) throws Jackso return (Year) p.getEmbeddedObject(); } // 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML) - if (t == JsonToken.START_OBJECT) { + if (p.isExpectedStartObjectToken()) { final String str = ctxt.extractScalarFromObject(p, this, handledType()); // 17-May-2025, tatu: [databind#4656] need to check for `null` if (str != null) { return _fromString(p, ctxt, str); } // fall through - } else if (p.hasToken(JsonToken.START_ARRAY)){ + } else if (p.isExpectedStartArrayToken()){ return _deserializeFromArray(p, ctxt); } return _handleUnexpectedToken(ctxt, p, JsonToken.VALUE_STRING, JsonToken.VALUE_NUMBER_INT); From 5480041ea9b23a4823f96b6a4110b857a37be037 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 5 May 2026 13:59:46 -0700 Subject: [PATCH 6/6] Refactor --- .../ext/javatime/deser/MonthDeserializer.java | 49 ++----------------- .../javatime/deser/MonthDeserializerTest.java | 22 ++++----- 2 files changed, 14 insertions(+), 57 deletions(-) diff --git a/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java index 8afc61cb26..5fe768b67d 100644 --- a/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java +++ b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java @@ -8,8 +8,6 @@ import tools.jackson.core.*; import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.DeserializationFeature; -import tools.jackson.databind.cfg.CoercionAction; import tools.jackson.databind.cfg.DateTimeFeature; import com.fasterxml.jackson.annotation.JsonFormat; @@ -80,50 +78,9 @@ public Month deserialize(JsonParser p, DeserializationContext ctxt) } // fall through } else if (p.isExpectedStartArrayToken()) { - // [databind#5957]: Respect coercion settings; do not accept empty or single-element - // arrays unless the corresponding DeserializationFeature is enabled. - JsonToken t = p.nextToken(); - if (t == JsonToken.END_ARRAY) { - final CoercionAction act = _findCoercionFromEmptyArray(ctxt); - if (act == CoercionAction.AsNull || act == CoercionAction.TryConvert) { - return null; - } - if (act == CoercionAction.AsEmpty) { - return (Month) getEmptyValue(ctxt); - } - return (Month) ctxt.handleUnexpectedToken(getValueType(ctxt), - JsonToken.START_ARRAY, p, "Cannot deserialize `Month` from empty JSON Array"); - } - // Validate token shape before checking UNWRAP, so inputs like [true], [1.5] - // or [{}] get a token-type diagnostic instead of a misleading suggestion to - // enable UNWRAP_SINGLE_VALUE_ARRAYS (which would not help). - if (t != JsonToken.VALUE_NUMBER_INT - && t != JsonToken.VALUE_STRING - && t != JsonToken.VALUE_EMBEDDED_OBJECT) { - return _reportWrongToken(ctxt, JsonToken.VALUE_NUMBER_INT, "month"); - } - if (!ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - return (Month) ctxt.handleUnexpectedToken(getValueType(ctxt), - JsonToken.START_ARRAY, p, - "Cannot deserialize `Month` out of START_ARRAY token: enable `DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS`"); - } - if (t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT) { - final Month parsed = deserialize(p, ctxt); - if (p.nextToken() != JsonToken.END_ARRAY) { - handleMissingEndArrayForSingle(p, ctxt); - } - return parsed; - } - int month = p.getIntValue(); - if (p.nextToken() != JsonToken.END_ARRAY) { - throw ctxt.wrongTokenException(p, handledType(), JsonToken.END_ARRAY, - "Expected array to end"); - } - if (Month.JANUARY.getValue() <= month && month <= Month.DECEMBER.getValue()) { - return Month.of(month); - } - return (Month) ctxt.handleWeirdNumberValue(handledType(), - month, "month number outside 1-12 range for 1-based `Month`s"); + // [databind#5957]: Delegate to standard array handling so empty arrays + // and single-element unwrapping respect coercion / UNWRAP_SINGLE_VALUE_ARRAYS. + return _deserializeFromArray(p, ctxt); } else if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) { return (Month) p.getEmbeddedObject(); } diff --git a/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java b/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java index 6ed3a7ea0f..70bce480d7 100644 --- a/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java +++ b/src/test/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializerTest.java @@ -285,7 +285,7 @@ public void testDeserializationAsArrayWithIntValue() throws Exception assertError( () -> readerForOneBased().readValue("[3]"), MismatchedInputException.class, - "UNWRAP_SINGLE_VALUE_ARRAYS" + "from Array value" ); } @@ -305,29 +305,29 @@ public void testDeserializationAsArrayWithIntValue_zeroBased() throws Exception assertError( () -> readerForZeroBased().readValue("[3]"), MismatchedInputException.class, - "UNWRAP_SINGLE_VALUE_ARRAYS" + "from Array value" ); } @Test public void testDeserializationAsArrayWithMoreThanOneElement() throws Exception { - // [databind#5957]: Multi-element arrays fail with UNWRAP_SINGLE_VALUE_ARRAYS message + // [databind#5957]: Multi-element array (no UNWRAP) rejected as Array-token mismatch assertError( () -> readerForOneBased().readValue("[1, 2]"), MismatchedInputException.class, - "UNWRAP_SINGLE_VALUE_ARRAYS" + "from Array value" ); } @Test public void testDeserializationAsArrayWithWrongToken() throws Exception { - // [databind#5957]: Boolean is not a coercible token shape; report token-type mismatch + // [databind#5957]: Boolean in array (no UNWRAP) rejected as Array-token mismatch assertError( () -> readerForOneBased().readValue("[true]"), MismatchedInputException.class, - "Expected VALUE_NUMBER_INT" + "from Array value" ); } @@ -338,29 +338,29 @@ public void testDeserializationAsArrayWithStringUnwrapDisabled() throws Exceptio assertError( () -> readerForOneBased().readValue("[\"JANUARY\"]"), MismatchedInputException.class, - "UNWRAP_SINGLE_VALUE_ARRAYS" + "from Array value" ); } @Test public void testDeserializationAsArrayWithFloatUnwrapDisabled() throws Exception { - // [databind#5957]: Float is not a coercible token shape; report token-type mismatch + // [databind#5957]: Float in array (no UNWRAP) rejected as Array-token mismatch assertError( () -> readerForOneBased().readValue("[1.5]"), MismatchedInputException.class, - "Expected VALUE_NUMBER_INT" + "from Array value" ); } @Test public void testDeserializationAsArrayWithObjectUnwrapDisabled() throws Exception { - // [databind#5957]: Object is not a coercible token shape; report token-type mismatch + // [databind#5957]: Object in array (no UNWRAP) rejected as Array-token mismatch assertError( () -> readerForOneBased().readValue("[{}]"), MismatchedInputException.class, - "Expected VALUE_NUMBER_INT" + "from Array value" ); }