Skip to content

Commit c378ccf

Browse files
author
Boleslav Bobcik
committed
Fix FasterXML#364: make one-based Month deserializer accept "12"
1 parent 543a5ba commit c378ccf

File tree

2 files changed

+68
-32
lines changed

2 files changed

+68
-32
lines changed

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

+33-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,51 @@
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+
if (_isPossibleNumericValue(token)) {
27+
String monthSpec = parser.getText();
28+
int oneBasedMonthNumber = _decodeNumber(monthSpec);
29+
if (1 <= oneBasedMonthNumber && oneBasedMonthNumber <= 12) {
30+
return Month.of(oneBasedMonthNumber);
31+
} else if (oneBasedMonthNumber >= 0) {
32+
throw new InvalidFormatException(parser, "Month number " + oneBasedMonthNumber + " not allowed for 1-based Month.", oneBasedMonthNumber, Integer.class);
33+
}
3534
}
36-
return zeroBaseMonth.minus(1);
35+
return getDelegatee().deserialize(parser, context);
3736
}
3837

39-
private boolean _isNumericValue(String text, JsonToken token) {
40-
return token == JsonToken.VALUE_NUMBER_INT || _isNumberAsString(text, token);
38+
private boolean _isPossibleNumericValue(JsonToken token) {
39+
return token == JsonToken.VALUE_NUMBER_INT || token == JsonToken.VALUE_STRING;
4140
}
4241

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

4766
@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)