From 6115ef32044992bb57a9584bdd74bfa2c55ab6a5 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 26 Apr 2025 23:39:39 +0900 Subject: [PATCH 1/6] Remove One-based... --- .../ext/javatime/JavaTimeInitializer.java | 2 + .../deser/JavaTimeDeserializerModifier.java | 9 +- .../ext/javatime/deser/MonthDeserializer.java | 117 ++++++++++++++++++ .../deser/OneBasedMonthDeserializer.java | 4 +- 4 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java diff --git a/src/main/java/tools/jackson/databind/ext/javatime/JavaTimeInitializer.java b/src/main/java/tools/jackson/databind/ext/javatime/JavaTimeInitializer.java index 618e3e680d..3d75c06ca4 100644 --- a/src/main/java/tools/jackson/databind/ext/javatime/JavaTimeInitializer.java +++ b/src/main/java/tools/jackson/databind/ext/javatime/JavaTimeInitializer.java @@ -102,6 +102,7 @@ public void setupModule(JacksonModule.SetupContext context) { .addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE) .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE) .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE) + .addDeserializer(Month.class, MonthDeserializer.INSTANCE) .addDeserializer(MonthDay.class, MonthDayDeserializer.INSTANCE) .addDeserializer(OffsetTime.class, OffsetTimeDeserializer.INSTANCE) .addDeserializer(Period.class, JSR310StringParsableDeserializer.PERIOD) @@ -118,6 +119,7 @@ public void setupModule(JacksonModule.SetupContext context) { .addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE) .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE) .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE) +// .addSerializer(Month.class, MonthSerializer.INSTANCE) .addSerializer(MonthDay.class, MonthDaySerializer.INSTANCE) .addSerializer(OffsetDateTime.class, OffsetDateTimeSerializer.INSTANCE) .addSerializer(OffsetTime.class, OffsetTimeSerializer.INSTANCE) diff --git a/src/main/java/tools/jackson/databind/ext/javatime/deser/JavaTimeDeserializerModifier.java b/src/main/java/tools/jackson/databind/ext/javatime/deser/JavaTimeDeserializerModifier.java index 831900a7f6..ff333052de 100644 --- a/src/main/java/tools/jackson/databind/ext/javatime/deser/JavaTimeDeserializerModifier.java +++ b/src/main/java/tools/jackson/databind/ext/javatime/deser/JavaTimeDeserializerModifier.java @@ -1,13 +1,8 @@ package tools.jackson.databind.ext.javatime.deser; -import java.time.Month; - import tools.jackson.databind.*; import tools.jackson.databind.deser.ValueDeserializerModifier; -/* 08-Apr-2025, tatu: we really should rewrite things to have "native" - * {@code MonthDeserializer} and not rely on {@code EnumDeserializer}. - */ public class JavaTimeDeserializerModifier extends ValueDeserializerModifier { private static final long serialVersionUID = 1L; @@ -17,9 +12,7 @@ public JavaTimeDeserializerModifier() { } @Override public ValueDeserializer modifyEnumDeserializer(DeserializationConfig config, JavaType type, BeanDescription.Supplier beanDescRef, ValueDeserializer defaultDeserializer) { - if (type.hasRawClass(Month.class)) { - return new OneBasedMonthDeserializer(defaultDeserializer); - } + return defaultDeserializer; } } 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 new file mode 100644 index 0000000000..eabc1b3838 --- /dev/null +++ b/src/main/java/tools/jackson/databind/ext/javatime/deser/MonthDeserializer.java @@ -0,0 +1,117 @@ +package tools.jackson.databind.ext.javatime.deser; + +import java.time.*; +import java.time.format.DateTimeFormatter; + +import tools.jackson.core.*; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.DeserializationFeature; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * Deserializer for Java 8 temporal {@link Month}s. + */ +public class MonthDeserializer extends JSR310DateTimeDeserializerBase +{ + public static final MonthDeserializer INSTANCE = new MonthDeserializer(); + + /** + * NOTE: only {@code public} so that use via annotations (see [modules-java8#202]) + * is possible + */ + public MonthDeserializer() { + this(null); + } + + public MonthDeserializer(DateTimeFormatter formatter) { + super(Month.class, formatter); + } + + protected MonthDeserializer(MonthDeserializer base, Boolean leniency) { + super(base, leniency); + } + + protected MonthDeserializer(MonthDeserializer base, + Boolean leniency, + DateTimeFormatter formatter, + JsonFormat.Shape shape) { + super(base, leniency, formatter, shape); + } + + @Override + protected MonthDeserializer withLeniency(Boolean leniency) { + return new MonthDeserializer(this, leniency); + } + + @Override + protected MonthDeserializer withDateFormat(DateTimeFormatter dtf) { + return new MonthDeserializer(this, _isLenient, dtf, _shape); + } + + @Override + public Month deserialize(JsonParser parser, DeserializationContext context) + throws JacksonException + { + if (parser.hasToken(JsonToken.VALUE_STRING)) { + return _fromString(parser, context, parser.getString()); + } + // 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML) + if (parser.isExpectedStartObjectToken()) { + return _fromString(parser, context, + context.extractScalarFromObject(parser, this, handledType())); + } + if (parser.isExpectedStartArrayToken()) { + JsonToken t = parser.nextToken(); + if (t == JsonToken.END_ARRAY) { + return null; + } + if ((t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT) + && context.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + final Month parsed = deserialize(parser, context); + if (parser.nextToken() != JsonToken.END_ARRAY) { + handleMissingEndArrayForSingle(parser, context); + } + return parsed; + } + if (t != JsonToken.VALUE_NUMBER_INT) { + _reportWrongToken(context, JsonToken.VALUE_NUMBER_INT, "month"); + } + int month = parser.getIntValue(); + if (parser.nextToken() != JsonToken.END_ARRAY) { + throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY, + "Expected array to end"); + } + return Month.of(month); + } + if (parser.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) { + return (Month) parser.getEmbeddedObject(); + } + return _handleUnexpectedToken(context, parser, + JsonToken.VALUE_STRING, JsonToken.START_ARRAY); + } + + protected Month _fromString(JsonParser p, DeserializationContext ctxt, + String string0) + throws JacksonException + { + String string = string0.trim(); + if (string.length() == 0) { + // 22-Oct-2020, tatu: not sure if we should pass original (to distinguish + // b/w empty and blank); for now don't which will allow blanks to be + // handled like "regular" empty (same as pre-2.12) + return _fromEmptyString(p, ctxt, string); + } + try { + if (_formatter == null) { + return Month.values()[Integer.parseInt(string)]; + } + return Month.from(_formatter.parse(string)); + } catch (DateTimeException e) { + return _handleDateTimeFormatException(ctxt, e, _formatter, string); + } catch (NumberFormatException e) { + throw ctxt.weirdStringException(string, handledType(), + "not a valid month value"); + } + } +} diff --git a/src/main/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserializer.java b/src/main/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserializer.java index 566c3d3f7f..957c843518 100644 --- a/src/main/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserializer.java +++ b/src/main/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserializer.java @@ -4,8 +4,8 @@ import tools.jackson.core.JsonParser; import tools.jackson.core.JsonToken; - -import tools.jackson.databind.*; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.ValueDeserializer; import tools.jackson.databind.cfg.DateTimeFeature; import tools.jackson.databind.deser.std.DelegatingDeserializer; import tools.jackson.databind.exc.InvalidFormatException; From c6b4f7ec2ee8f484887988790a1107c2fb3cd2fb Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 27 Apr 2025 00:44:15 +0900 Subject: [PATCH 2/6] Finalize error messages and sorts --- .../ext/javatime/deser/MonthDeserializer.java | 58 ++++++++++++++++++- .../deser/OneBasedMonthDeserTest.java | 13 +++-- 2 files changed, 63 insertions(+), 8 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 eabc1b3838..748ae883d8 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 @@ -2,10 +2,14 @@ import java.time.*; import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; import tools.jackson.core.*; import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.cfg.DateTimeFeature; +import tools.jackson.databind.exc.InvalidFormatException; import com.fasterxml.jackson.annotation.JsonFormat; @@ -16,6 +20,8 @@ public class MonthDeserializer extends JSR310DateTimeDeserializerBase { public static final MonthDeserializer INSTANCE = new MonthDeserializer(); + private final Set possibleMonthStringValues = Arrays.stream(Month.values()).map(Month::name).collect(Collectors.toSet()); + /** * NOTE: only {@code public} so that use via annotations (see [modules-java8#202]) * is possible @@ -56,6 +62,20 @@ public Month deserialize(JsonParser parser, DeserializationContext context) if (parser.hasToken(JsonToken.VALUE_STRING)) { return _fromString(parser, context, parser.getString()); } + // Support numeric scalar input + if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) { + final int raw = parser.getIntValue(); + if (context.isEnabled(DateTimeFeature.ONE_BASED_MONTHS)) { + return _decodeMonth(raw, context); + } + // default: 0‑based index (0 == JANUARY) + if (raw < 0 || raw >= 12) { + context.handleWeirdNumberValue(handledType(), + raw, "Month index (%s) outside 0-11 range", raw); + return null; // never gets here, but compiler doesn't know + } + return Month.values()[raw]; + } // 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML) if (parser.isExpectedStartObjectToken()) { return _fromString(parser, context, @@ -75,7 +95,7 @@ public Month deserialize(JsonParser parser, DeserializationContext context) return parsed; } if (t != JsonToken.VALUE_NUMBER_INT) { - _reportWrongToken(context, JsonToken.VALUE_NUMBER_INT, "month"); + _reportWrongToken(context, JsonToken.VALUE_NUMBER_INT, Integer.class.getName()); } int month = parser.getIntValue(); if (parser.nextToken() != JsonToken.END_ARRAY) { @@ -104,7 +124,26 @@ protected Month _fromString(JsonParser p, DeserializationContext ctxt, } try { if (_formatter == null) { - return Month.values()[Integer.parseInt(string)]; + // First: try purely numeric input + try { + int oneBasedMonthNumber = Integer.parseInt(string); + if (ctxt.isEnabled(DateTimeFeature.ONE_BASED_MONTHS)) { + return _decodeMonth(oneBasedMonthNumber, ctxt); + } + if (oneBasedMonthNumber < 0 || oneBasedMonthNumber >= 12) { // invalid for 0‑based + throw new InvalidFormatException(p, "Month number " + oneBasedMonthNumber + " not allowed for 1-based Month.", oneBasedMonthNumber, Integer.class); + } + return Month.values()[oneBasedMonthNumber]; // 0‑based mapping + } catch (NumberFormatException nfe) { + // fall through – treat as textual month name + } + // Second: try textual input + // Handle English month names such as "JANUARY" from the actual Month Enum names + if (possibleMonthStringValues.contains(string)) { + return Month.valueOf(string); + } else { + throw new InvalidFormatException(p, String.format("Cannot deserialize value of type `java.time.Month` from String \"%s\": not one of the values accepted for Enum class: %s", string, Arrays.toString(Month.values())), string, Month.class); + } } return Month.from(_formatter.parse(string)); } catch (DateTimeException e) { @@ -114,4 +153,19 @@ protected Month _fromString(JsonParser p, DeserializationContext ctxt, "not a valid month value"); } } + + /** + * Validate and convert a 1‑based month number to {@link Month}. + */ + private Month _decodeMonth(int oneBasedMonthNumber, DeserializationContext ctxt) + throws JacksonException + { + if (Month.JANUARY.getValue() <= oneBasedMonthNumber && oneBasedMonthNumber <= Month.DECEMBER.getValue()) { + return Month.of(oneBasedMonthNumber); + } + // If out of range, throw an exception + ctxt.handleWeirdNumberValue(handledType(), + oneBasedMonthNumber, "Month number %s not allowed for 1-based Month.", oneBasedMonthNumber); + return null; // never gets here, but compiler doesn't know + } } diff --git a/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java b/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java index 684fcb4427..366550b0e9 100644 --- a/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java +++ b/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java @@ -177,12 +177,13 @@ public void testDeserializeFromEmptyString() throws Exception assertNull(m); // But coercion from empty String not enabled for Enums by default: - try { - mapper.readerFor(Month.class).readValue("\"\""); - fail("Should not pass"); - } catch (MismatchedInputException e) { - verifyException(e, "Cannot coerce empty String"); - } + // TODO : Figure out how to make this pass. +// try { +// Month result = mapper.readerFor(Month.class).readValue("\"\""); +// fail("Should not pass"); +// } catch (MismatchedInputException e) { +// verifyException(e, "Cannot coerce empty String"); +// } // But can allow coercion of empty String to, say, null ObjectMapper emptyStringMapper = mapperBuilder() .withCoercionConfig(Month.class, From f33bbb0aad8cbee78e850bca239aa05bf415f11b Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 27 Apr 2025 00:44:38 +0900 Subject: [PATCH 3/6] Delete OneBasedMonthDeserializer.java --- .../deser/OneBasedMonthDeserializer.java | 76 ------------------- 1 file changed, 76 deletions(-) delete mode 100644 src/main/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserializer.java diff --git a/src/main/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserializer.java b/src/main/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserializer.java deleted file mode 100644 index 957c843518..0000000000 --- a/src/main/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserializer.java +++ /dev/null @@ -1,76 +0,0 @@ -package tools.jackson.databind.ext.javatime.deser; - -import java.time.Month; - -import tools.jackson.core.JsonParser; -import tools.jackson.core.JsonToken; -import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.ValueDeserializer; -import tools.jackson.databind.cfg.DateTimeFeature; -import tools.jackson.databind.deser.std.DelegatingDeserializer; -import tools.jackson.databind.exc.InvalidFormatException; - -public class OneBasedMonthDeserializer extends DelegatingDeserializer { - public OneBasedMonthDeserializer(ValueDeserializer defaultDeserializer) { - super(defaultDeserializer); - } - - @Override - public Object deserialize(JsonParser parser, DeserializationContext context) { - final boolean oneBased = context.isEnabled(DateTimeFeature.ONE_BASED_MONTHS); - if (oneBased) { - JsonToken token = parser.currentToken(); - switch (token) { - case VALUE_NUMBER_INT: - return _decodeMonth(parser.getIntValue(), parser); - case VALUE_STRING: - String monthSpec = parser.getString(); - int oneBasedMonthNumber = _decodeNumber(monthSpec); - if (oneBasedMonthNumber >= 0) { - return _decodeMonth(oneBasedMonthNumber, parser); - } - default: - // Otherwise fall through to default handling - break; - } - // fall-through - } - return getDelegatee().deserialize(parser, context); - } - - /** - * @return Numeric value of input text that represents a 1-digit or 2-digit number. - * Negative value in other cases (empty string, not a number, 3 or more digits). - */ - private int _decodeNumber(String text) { - int numValue; - switch (text.length()) { - case 1: - char c = text.charAt(0); - boolean cValid = ('0' <= c && c <= '9'); - numValue = cValid ? (c - '0') : -1; - break; - case 2: - char c1 = text.charAt(0); - char c2 = text.charAt(1); - boolean c12valid = ('0' <= c1 && c1 <= '9' && '0' <= c2 && c2 <= '9'); - numValue = c12valid ? (10 * (c1 - '0') + (c2 - '0')) : -1; - break; - default: - numValue = -1; - } - return numValue; - } - - private Month _decodeMonth(int oneBasedMonthNumber, JsonParser parser) throws InvalidFormatException { - if (Month.JANUARY.getValue() <= oneBasedMonthNumber && oneBasedMonthNumber <= Month.DECEMBER.getValue()) { - return Month.of(oneBasedMonthNumber); - } - throw new InvalidFormatException(parser, "Month number " + oneBasedMonthNumber + " not allowed for 1-based Month.", oneBasedMonthNumber, Integer.class); - } - - @Override - protected ValueDeserializer newDelegatingInstance(ValueDeserializer newDelegatee) { - return new OneBasedMonthDeserializer(newDelegatee); - } -} From 85b9bd0eec99c3f8a18f67ea7279ff09bd7f543f Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 26 Apr 2025 11:21:44 -0700 Subject: [PATCH 4/6] Remove unneeded class --- .../ext/javatime/JavaTimeInitializer.java | 1 - .../deser/JavaTimeDeserializerModifier.java | 18 ------------------ .../javatime/deser/OneBasedMonthDeserTest.java | 1 - 3 files changed, 20 deletions(-) delete mode 100644 src/main/java/tools/jackson/databind/ext/javatime/deser/JavaTimeDeserializerModifier.java diff --git a/src/main/java/tools/jackson/databind/ext/javatime/JavaTimeInitializer.java b/src/main/java/tools/jackson/databind/ext/javatime/JavaTimeInitializer.java index 3d75c06ca4..0bd99d9c71 100644 --- a/src/main/java/tools/jackson/databind/ext/javatime/JavaTimeInitializer.java +++ b/src/main/java/tools/jackson/databind/ext/javatime/JavaTimeInitializer.java @@ -161,7 +161,6 @@ public void setupModule(JacksonModule.SetupContext context) { // [modules-java8#274]: 1-based Month (de)serializer need to be applied via modifiers: // [databind#5078]: Should rewrite not to require this - context.addDeserializerModifier(new JavaTimeDeserializerModifier()); context.addSerializerModifier(new JavaTimeSerializerModifier()); context.addValueInstantiators(new ValueInstantiators.Base() { diff --git a/src/main/java/tools/jackson/databind/ext/javatime/deser/JavaTimeDeserializerModifier.java b/src/main/java/tools/jackson/databind/ext/javatime/deser/JavaTimeDeserializerModifier.java deleted file mode 100644 index ff333052de..0000000000 --- a/src/main/java/tools/jackson/databind/ext/javatime/deser/JavaTimeDeserializerModifier.java +++ /dev/null @@ -1,18 +0,0 @@ -package tools.jackson.databind.ext.javatime.deser; - -import tools.jackson.databind.*; -import tools.jackson.databind.deser.ValueDeserializerModifier; - -public class JavaTimeDeserializerModifier extends ValueDeserializerModifier -{ - private static final long serialVersionUID = 1L; - - public JavaTimeDeserializerModifier() { } - - @Override - public ValueDeserializer modifyEnumDeserializer(DeserializationConfig config, JavaType type, - BeanDescription.Supplier beanDescRef, ValueDeserializer defaultDeserializer) { - - return defaultDeserializer; - } -} diff --git a/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java b/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java index 366550b0e9..5403015f6f 100644 --- a/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java +++ b/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java @@ -15,7 +15,6 @@ import tools.jackson.databind.cfg.CoercionInputShape; import tools.jackson.databind.cfg.DateTimeFeature; import tools.jackson.databind.exc.InvalidFormatException; -import tools.jackson.databind.exc.MismatchedInputException; import tools.jackson.databind.ext.javatime.DateTimeTestBase; import tools.jackson.databind.ext.javatime.MockObjectConfiguration; import tools.jackson.databind.json.JsonMapper; From ab630eefa4b63a35c3d56aee2d07983ca5ae1e81 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 26 Apr 2025 11:27:23 -0700 Subject: [PATCH 5/6] ... --- .../databind/ext/javatime/deser/OneBasedMonthDeserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java b/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java index 5403015f6f..9ca4ce8e6b 100644 --- a/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java +++ b/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java @@ -179,7 +179,7 @@ public void testDeserializeFromEmptyString() throws Exception // TODO : Figure out how to make this pass. // try { // Month result = mapper.readerFor(Month.class).readValue("\"\""); -// fail("Should not pass"); +// fail("Should not pass, but got: " + result); // } catch (MismatchedInputException e) { // verifyException(e, "Cannot coerce empty String"); // } From 99f0139d2f79b68ade278dbd873bec15858dc390 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 26 Apr 2025 11:47:37 -0700 Subject: [PATCH 6/6] Fix a commented-out test --- .../jackson/databind/cfg/CoercionConfigs.java | 4 +-- .../deser/OneBasedMonthDeserTest.java | 31 +++++++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/java/tools/jackson/databind/cfg/CoercionConfigs.java b/src/main/java/tools/jackson/databind/cfg/CoercionConfigs.java index 74873cf285..6e1152f5d7 100644 --- a/src/main/java/tools/jackson/databind/cfg/CoercionConfigs.java +++ b/src/main/java/tools/jackson/databind/cfg/CoercionConfigs.java @@ -158,8 +158,6 @@ public MutableCoercionConfig findOrCreateCoercion(Class type) { * @param inputShape Input shape to coerce from * * @return CoercionAction configured for specified coercion - * - * @since 2.12 */ public CoercionAction findCoercion(DeserializationConfig config, LogicalType targetType, @@ -221,7 +219,7 @@ public CoercionAction findCoercion(DeserializationConfig config, final boolean baseScalar = _isScalarType(targetType); if (baseScalar - // Default for setting in 2.x is true + // Default for setting in 2.x and 3.x is true && !config.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS) // 12-Oct-2022, carterkozak: As per [databind#3624]: Coercion from integer-shaped // data into a floating point type is not banned by the diff --git a/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java b/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java index 9ca4ce8e6b..8a67bc5c99 100644 --- a/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java +++ b/src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java @@ -9,12 +9,14 @@ import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EnumSource; +import tools.jackson.databind.MapperFeature; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.ObjectReader; import tools.jackson.databind.cfg.CoercionAction; import tools.jackson.databind.cfg.CoercionInputShape; import tools.jackson.databind.cfg.DateTimeFeature; import tools.jackson.databind.exc.InvalidFormatException; +import tools.jackson.databind.exc.MismatchedInputException; import tools.jackson.databind.ext.javatime.DateTimeTestBase; import tools.jackson.databind.ext.javatime.MockObjectConfiguration; import tools.jackson.databind.json.JsonMapper; @@ -92,8 +94,8 @@ static void assertError(Executable codeToRun, Class expecte } } - private final ObjectMapper MAPPER = newJsonMapper(); - + private final ObjectMapper MAPPER = newMapper(); + @Test public void testDeserialization01_zeroBased() throws Exception { @@ -169,20 +171,23 @@ public void testFormatAnnotation_oneBased() throws Exception @Test public void testDeserializeFromEmptyString() throws Exception { - final ObjectMapper mapper = newMapper(); - // Nulls are handled in general way, not by deserializer so they are ok - Month m = mapper.readerFor(Month.class).readValue(" null "); + Month m = MAPPER.readerFor(Month.class).readValue(" null "); assertNull(m); - // But coercion from empty String not enabled for Enums by default: - // TODO : Figure out how to make this pass. -// try { -// Month result = mapper.readerFor(Month.class).readValue("\"\""); -// fail("Should not pass, but got: " + result); -// } catch (MismatchedInputException e) { -// verifyException(e, "Cannot coerce empty String"); -// } + // Although coercion from empty String not enabled for Enums by default, + // it IS for Scalars (when `MapperFeature.ALLOW_COERCION_OF_SCALARS` enabled + // which it is by default). So need to disable it here: + // (we no longer consider `Month` as LogicalType.Enum but LogicalType.DateTime) + try { + ObjectMapper strictMapper = mapperBuilder() + .disable(MapperFeature.ALLOW_COERCION_OF_SCALARS) + .build(); + Month result = strictMapper.readerFor(Month.class).readValue("\"\""); + fail("Should not pass, but got: " + result); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot coerce empty String"); + } // But can allow coercion of empty String to, say, null ObjectMapper emptyStringMapper = mapperBuilder() .withCoercionConfig(Month.class,