Skip to content

Commit 6283ad1

Browse files
authored
Fix #364: make one-based Month deserializer accept "12" (#365)
1 parent 543a5ba commit 6283ad1

File tree

2 files changed

+74
-32
lines changed

2 files changed

+74
-32
lines changed

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OneBasedMonthDeserializer.java

+39-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import java.io.IOException;
44
import java.time.Month;
5-
import java.util.regex.Pattern;
65

76
import com.fasterxml.jackson.core.JsonParser;
87
import com.fasterxml.jackson.core.JsonToken;
@@ -17,31 +16,57 @@
1716
public class OneBasedMonthDeserializer extends DelegatingDeserializer {
1817
private static final long serialVersionUID = 1L;
1918

20-
private static final Pattern HAS_ONE_OR_TWO_DIGITS = Pattern.compile("^\\d{1,2}$");
21-
2219
public OneBasedMonthDeserializer(JsonDeserializer<?> defaultDeserializer) {
2320
super(defaultDeserializer);
2421
}
2522

2623
@Override
2724
public Object deserialize(JsonParser parser, DeserializationContext context) throws IOException {
2825
JsonToken token = parser.currentToken();
29-
Month zeroBaseMonth = (Month) getDelegatee().deserialize(parser, context);
30-
if (!_isNumericValue(parser.getText(), token)) {
31-
return zeroBaseMonth;
32-
}
33-
if (zeroBaseMonth == Month.JANUARY) {
34-
throw new InvalidFormatException(parser, "Month.JANUARY value not allowed for 1-based Month.", zeroBaseMonth, Month.class);
26+
switch (token) {
27+
case VALUE_NUMBER_INT:
28+
return _decodeMonth(parser.getIntValue(), parser);
29+
case VALUE_STRING:
30+
String monthSpec = parser.getText();
31+
int oneBasedMonthNumber = _decodeNumber(monthSpec);
32+
if (oneBasedMonthNumber >= 0) {
33+
return _decodeMonth(oneBasedMonthNumber, parser);
34+
}
35+
// Otherwise fall through to default handling
36+
break;
3537
}
36-
return zeroBaseMonth.minus(1);
38+
return getDelegatee().deserialize(parser, context);
3739
}
3840

39-
private boolean _isNumericValue(String text, JsonToken token) {
40-
return token == JsonToken.VALUE_NUMBER_INT || _isNumberAsString(text, token);
41+
/**
42+
* @return Numeric value of input text that represents a 1-digit or 2-digit number.
43+
* Negative value in other cases (empty string, not a number, 3 or more digits).
44+
*/
45+
private int _decodeNumber(String text) {
46+
int numValue;
47+
switch (text.length()) {
48+
case 1:
49+
char c = text.charAt(0);
50+
boolean cValid = ('0' <= c && c <= '9');
51+
numValue = cValid ? (c - '0') : -1;
52+
break;
53+
case 2:
54+
char c1 = text.charAt(0);
55+
char c2 = text.charAt(1);
56+
boolean c12valid = ('0' <= c1 && c1 <= '9' && '0' <= c2 && c2 <= '9');
57+
numValue = c12valid ? (10 * (c1 - '0') + (c2 - '0')) : -1;
58+
break;
59+
default:
60+
numValue = -1;
61+
}
62+
return numValue;
4163
}
4264

43-
private boolean _isNumberAsString(String text, JsonToken token) {
44-
return token == JsonToken.VALUE_STRING && HAS_ONE_OR_TWO_DIGITS.matcher(text).matches();
65+
private Month _decodeMonth(int oneBasedMonthNumber, JsonParser parser) throws InvalidFormatException {
66+
if (Month.JANUARY.getValue() <= oneBasedMonthNumber && oneBasedMonthNumber <= Month.DECEMBER.getValue()) {
67+
return Month.of(oneBasedMonthNumber);
68+
}
69+
throw new InvalidFormatException(parser, "Month number " + oneBasedMonthNumber + " not allowed for 1-based Month.", oneBasedMonthNumber, Integer.class);
4570
}
4671

4772
@Override

datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OneBasedMonthDeserTest.java

+35-18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
1818
import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration;
1919
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
20+
import org.junit.jupiter.params.ParameterizedTest;
21+
import org.junit.jupiter.params.provider.CsvSource;
22+
import org.junit.jupiter.params.provider.EnumSource;
2023

2124
import static org.junit.jupiter.api.Assertions.*;
2225

@@ -29,37 +32,51 @@ static class Wrapper {
2932
public Wrapper() { }
3033
}
3134

32-
@Test
33-
public void testDeserializationAsString01_oneBased() throws Exception
35+
@ParameterizedTest
36+
@EnumSource(Month.class)
37+
public void testDeserializationAsString01_oneBased(Month expectedMonth) throws Exception
3438
{
35-
assertEquals(Month.JANUARY, readerForOneBased().readValue("\"1\""));
39+
int monthNum = expectedMonth.getValue();
40+
assertEquals(expectedMonth, readerForOneBased().readValue("\"" + monthNum + '"'));
3641
}
3742

38-
@Test
39-
public void testDeserializationAsString01_zeroBased() throws Exception
43+
@ParameterizedTest
44+
@EnumSource(Month.class)
45+
public void testDeserializationAsString01_zeroBased(Month expectedMonth) throws Exception
4046
{
41-
assertEquals(Month.FEBRUARY, readerForZeroBased().readValue("\"1\""));
47+
int monthNum = expectedMonth.ordinal();
48+
assertEquals(expectedMonth, readerForZeroBased().readValue("\"" + monthNum + '"'));
4249
}
4350

4451

45-
@Test
46-
public void testDeserializationAsString02_oneBased() throws Exception
52+
@ParameterizedTest
53+
@EnumSource(Month.class)
54+
public void testDeserializationAsString02_oneBased(Month month) throws Exception
4755
{
48-
assertEquals(Month.JANUARY, readerForOneBased().readValue("\"JANUARY\""));
56+
assertEquals(month, readerForOneBased().readValue("\"" + month.name() + '"'));
4957
}
5058

51-
@Test
52-
public void testDeserializationAsString02_zeroBased() throws Exception
59+
@ParameterizedTest
60+
@EnumSource(Month.class)
61+
public void testDeserializationAsString02_zeroBased(Month month) throws Exception
5362
{
54-
assertEquals(Month.JANUARY, readerForZeroBased().readValue("\"JANUARY\""));
55-
}
56-
57-
@Test
58-
public void testBadDeserializationAsString01_oneBased() {
63+
assertEquals(month, readerForOneBased().readValue("\"" + month.name() + '"'));
64+
}
65+
66+
@ParameterizedTest
67+
@CsvSource({
68+
"notamonth , 'Cannot deserialize value of type `java.time.Month` from String \"notamonth\": not one of the values accepted for Enum class: [OCTOBER, SEPTEMBER, JUNE, MARCH, MAY, APRIL, JULY, JANUARY, FEBRUARY, DECEMBER, AUGUST, NOVEMBER]'",
69+
"JANUAR , 'Cannot deserialize value of type `java.time.Month` from String \"JANUAR\": not one of the values accepted for Enum class: [OCTOBER, SEPTEMBER, JUNE, MARCH, MAY, APRIL, JULY, JANUARY, FEBRUARY, DECEMBER, AUGUST, NOVEMBER]'",
70+
"march , 'Cannot deserialize value of type `java.time.Month` from String \"march\": not one of the values accepted for Enum class: [OCTOBER, SEPTEMBER, JUNE, MARCH, MAY, APRIL, JULY, JANUARY, FEBRUARY, DECEMBER, AUGUST, NOVEMBER]'",
71+
"0 , 'Month number 0 not allowed for 1-based Month.'",
72+
"13 , 'Month number 13 not allowed for 1-based Month.'",
73+
})
74+
public void testBadDeserializationAsString01_oneBased(String monthSpec, String expectedMessage) {
75+
String value = "\"" + monthSpec + '"';
5976
assertError(
60-
() -> readerForOneBased().readValue("\"notamonth\""),
77+
() -> readerForOneBased().readValue(value),
6178
InvalidFormatException.class,
62-
"Cannot deserialize value of type `java.time.Month` from String \"notamonth\": not one of the values accepted for Enum class: [OCTOBER, SEPTEMBER, JUNE, MARCH, MAY, APRIL, JULY, JANUARY, FEBRUARY, DECEMBER, AUGUST, NOVEMBER]"
79+
expectedMessage
6380
);
6481
}
6582

0 commit comments

Comments
 (0)