Skip to content

Commit 53a7477

Browse files
authored
Fixes #5065: default change for DateTimeFeature.ONE_BASED_MONTHS (#5077)
1 parent 0b0756f commit 53a7477

File tree

10 files changed

+68
-83
lines changed

10 files changed

+68
-83
lines changed

release-notes/VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Versions: 3.x (for earlier see VERSION-2.x)
77

88
3.0.0-rc3 (not yet released)
99

10+
#5065: Change default for `DateTimeFeature.ONE_BASED_MONTHS` to `true` in 3.0
1011
- Branch rename "master" -> "3.x" [JSTEP-12]
1112

1213
3.0.0-rc3 (not yet released)

src/main/java/tools/jackson/databind/cfg/DateTimeFeature.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,20 @@ public enum DateTimeFeature implements DatatypeFeature
3434
NORMALIZE_DESERIALIZED_ZONE_ID(true),
3535

3636
/**
37-
* Feature that determines whether {@link java.time.Month} is serialized
38-
* and deserialized as using a zero-based index (FALSE) or a one-based index (TRUE).
39-
* For example, "1" would be serialized/deserialized as Month.JANUARY if TRUE and Month.FEBRUARY if FALSE.
37+
* Feature that determines whether {@link java.time.Month} is serialized as
38+
* and deserialized expected a zero- ({@code false}) or one-based index ({@code true}).
39+
* For example, {@code Month.JANUARY} would be serialized as {@code 1} when enabled
40+
* but as {@code 0} when disabled.
41+
* Conversely JSON Number {@code 1} would be deserialized as {@code Month.JANUARY}
42+
* when enabled, but as {@code Month.FEBRUARY} when disabled.
4043
*<p>
41-
* Default setting is false, meaning that Month is serialized/deserialized as a zero-based index.
44+
* Default setting is {@code true}, meaning that Month is serialized using one-based index
45+
* and deserialized expecting one-based index.
46+
*<p>
47+
* NOTE: default setting changed between Jackson 2.x (was {@code false}) and Jackson
48+
* 3.0 (changed to {@code true}).
4249
*/
43-
ONE_BASED_MONTHS(false),
50+
ONE_BASED_MONTHS(true),
4451

4552
/**
4653
* Feature that determines whether the {@link java.util.TimeZone} of the

src/main/java/tools/jackson/databind/cfg/MapperBuilder.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,9 @@ public B configureForJackson2() {
818818
.enable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
819819
.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
820820
.enable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
821-
.disable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
821+
.disable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
822+
.disable(DateTimeFeature.ONE_BASED_MONTHS)
823+
;
822824
}
823825

824826
/*

src/main/java/tools/jackson/databind/ext/javatime/JavaTimeInitializer.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import tools.jackson.databind.*;
2222
import tools.jackson.databind.cfg.DatatypeFeatures;
23-
import tools.jackson.databind.cfg.DateTimeFeature;
2423
import tools.jackson.databind.deser.ValueInstantiator;
2524
import tools.jackson.databind.deser.ValueInstantiators;
2625
import tools.jackson.databind.deser.std.StdValueInstantiator;
@@ -158,9 +157,9 @@ public void setupModule(JacksonModule.SetupContext context) {
158157
);
159158

160159
// [modules-java8#274]: 1-based Month (de)serializer need to be applied via modifiers:
161-
final boolean oneBasedMonthEnabled = context.isEnabled(DateTimeFeature.ONE_BASED_MONTHS);
162-
context.addDeserializerModifier(new JavaTimeDeserializerModifier(oneBasedMonthEnabled));
163-
context.addSerializerModifier(new JavaTimeSerializerModifier(oneBasedMonthEnabled));
160+
// [databind#5078]: Should rewrite not to require this
161+
context.addDeserializerModifier(new JavaTimeDeserializerModifier());
162+
context.addSerializerModifier(new JavaTimeSerializerModifier());
164163

165164
context.addValueInstantiators(new ValueInstantiators.Base() {
166165
@Override

src/main/java/tools/jackson/databind/ext/javatime/deser/JavaTimeDeserializerModifier.java

+6-9
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,19 @@
55
import tools.jackson.databind.*;
66
import tools.jackson.databind.deser.ValueDeserializerModifier;
77

8-
/**
9-
* @since 2.17
8+
/* 08-Apr-2025, tatu: we really should rewrite things to have "native"
9+
* {@code MonthDeserializer} and not rely on {@code EnumDeserializer}.
1010
*/
11-
public class JavaTimeDeserializerModifier extends ValueDeserializerModifier {
11+
public class JavaTimeDeserializerModifier extends ValueDeserializerModifier
12+
{
1213
private static final long serialVersionUID = 1L;
1314

14-
private final boolean _oneBaseMonths;
15-
16-
public JavaTimeDeserializerModifier(boolean oneBaseMonths) {
17-
_oneBaseMonths = oneBaseMonths;
18-
}
15+
public JavaTimeDeserializerModifier() { }
1916

2017
@Override
2118
public ValueDeserializer<?> modifyEnumDeserializer(DeserializationConfig config, JavaType type,
2219
BeanDescription beanDesc, ValueDeserializer<?> defaultDeserializer) {
23-
if (_oneBaseMonths && type.hasRawClass(Month.class)) {
20+
if (type.hasRawClass(Month.class)) {
2421
return new OneBasedMonthDeserializer(defaultDeserializer);
2522
}
2623
return defaultDeserializer;

src/main/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserializer.java

+18-13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import tools.jackson.core.JsonToken;
77

88
import tools.jackson.databind.*;
9+
import tools.jackson.databind.cfg.DateTimeFeature;
910
import tools.jackson.databind.deser.std.DelegatingDeserializer;
1011
import tools.jackson.databind.exc.InvalidFormatException;
1112

@@ -16,19 +17,23 @@ public OneBasedMonthDeserializer(ValueDeserializer<?> defaultDeserializer) {
1617

1718
@Override
1819
public Object deserialize(JsonParser parser, DeserializationContext context) {
19-
JsonToken token = parser.currentToken();
20-
switch (token) {
21-
case VALUE_NUMBER_INT:
22-
return _decodeMonth(parser.getIntValue(), parser);
23-
case VALUE_STRING:
24-
String monthSpec = parser.getString();
25-
int oneBasedMonthNumber = _decodeNumber(monthSpec);
26-
if (oneBasedMonthNumber >= 0) {
27-
return _decodeMonth(oneBasedMonthNumber, parser);
28-
}
29-
default:
30-
// Otherwise fall through to default handling
31-
break;
20+
final boolean oneBased = context.isEnabled(DateTimeFeature.ONE_BASED_MONTHS);
21+
if (oneBased) {
22+
JsonToken token = parser.currentToken();
23+
switch (token) {
24+
case VALUE_NUMBER_INT:
25+
return _decodeMonth(parser.getIntValue(), parser);
26+
case VALUE_STRING:
27+
String monthSpec = parser.getString();
28+
int oneBasedMonthNumber = _decodeNumber(monthSpec);
29+
if (oneBasedMonthNumber >= 0) {
30+
return _decodeMonth(oneBasedMonthNumber, parser);
31+
}
32+
default:
33+
// Otherwise fall through to default handling
34+
break;
35+
}
36+
// fall-through
3237
}
3338
return getDelegatee().deserialize(parser, context);
3439
}

src/main/java/tools/jackson/databind/ext/javatime/ser/JavaTimeSerializerModifier.java

+2-9
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,15 @@
55
import tools.jackson.databind.*;
66
import tools.jackson.databind.ser.ValueSerializerModifier;
77

8-
/**
9-
* @since 2.17
10-
*/
118
public class JavaTimeSerializerModifier extends ValueSerializerModifier {
129
private static final long serialVersionUID = 1L;
1310

14-
private final boolean _oneBaseMonths;
15-
16-
public JavaTimeSerializerModifier(boolean oneBaseMonths) {
17-
_oneBaseMonths = oneBaseMonths;
18-
}
11+
public JavaTimeSerializerModifier() { }
1912

2013
@Override
2114
public ValueSerializer<?> modifyEnumSerializer(SerializationConfig config, JavaType valueType,
2215
BeanDescription beanDesc, ValueSerializer<?> serializer) {
23-
if (_oneBaseMonths && valueType.hasRawClass(Month.class)) {
16+
if (valueType.hasRawClass(Month.class)) {
2417
return new OneBasedMonthSerializer(serializer);
2518
}
2619
return serializer;

src/main/java/tools/jackson/databind/ext/javatime/ser/OneBasedMonthSerializer.java

+12-10
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
import tools.jackson.core.JsonGenerator;
66

77
import tools.jackson.databind.*;
8+
import tools.jackson.databind.cfg.DateTimeFeature;
89

9-
/**
10-
* @since 2.17
11-
*/
12-
public class OneBasedMonthSerializer extends ValueSerializer<Month> {
10+
public class OneBasedMonthSerializer extends ValueSerializer<Month>
11+
{
1312
private final ValueSerializer<Object> _defaultSerializer;
1413

1514
@SuppressWarnings("unchecked")
@@ -21,12 +20,15 @@ public OneBasedMonthSerializer(ValueSerializer<?> defaultSerializer)
2120
@Override
2221
public void serialize(Month value, JsonGenerator gen, SerializationContext ctxt)
2322
{
24-
// 15-Jan-2024, tatu: [modules-java8#274] This is not really sufficient
25-
// (see `jackson-databind` `EnumSerializer` for full logic), but has to
26-
// do for now. May need to add `@JsonFormat.shape` handling in future.
27-
if (ctxt.isEnabled(SerializationFeature.WRITE_ENUMS_USING_INDEX)) {
28-
gen.writeNumber(value.ordinal() + 1);
29-
return;
23+
final boolean oneBased = ctxt.isEnabled(DateTimeFeature.ONE_BASED_MONTHS);
24+
if (oneBased) {
25+
// 15-Jan-2024, tatu: [modules-java8#274] This is not really sufficient
26+
// (see `jackson-databind` `EnumSerializer` for full logic), but has to
27+
// do for now. May need to add `@JsonFormat.shape` handling in future.
28+
if (ctxt.isEnabled(SerializationFeature.WRITE_ENUMS_USING_INDEX)) {
29+
gen.writeNumber(value.ordinal() + 1);
30+
return;
31+
}
3032
}
3133
_defaultSerializer.serialize(value, gen, ctxt);
3234
}

src/test/java/tools/jackson/databind/ext/javatime/DateTimeTestBase.java

+1-21
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import tools.jackson.databind.json.JsonMapper;
1010

1111
public class DateTimeTestBase
12+
extends tools.jackson.databind.testutil.DatabindTestUtil
1213
{
1314
protected static final ZoneId UTC = ZoneId.of("UTC");
1415

@@ -45,27 +46,6 @@ protected static JsonMapper.Builder mapperBuilder() {
4546
.disable(JsonWriteFeature.ESCAPE_FORWARD_SLASHES);
4647
}
4748

48-
protected String q(String value) {
49-
return "\"" + value + "\"";
50-
}
51-
52-
protected String a2q(String json) {
53-
return json.replace("'", "\"");
54-
}
55-
56-
protected void verifyException(Throwable e, String... matches)
57-
{
58-
String msg = e.getMessage();
59-
String lmsg = (msg == null) ? "" : msg.toLowerCase();
60-
for (String match : matches) {
61-
String lmatch = match.toLowerCase();
62-
if (lmsg.indexOf(lmatch) >= 0) {
63-
return;
64-
}
65-
}
66-
throw new Error("Expected an exception with one of substrings ("+Arrays.asList(matches)+"): got one with message \""+msg+"\"");
67-
}
68-
6949
protected static <T> Map<T, String> asMap(T key, String value) {
7050
return Collections.singletonMap(key, value);
7151
}

src/test/java/tools/jackson/databind/ext/javatime/deser/OneBasedMonthDeserTest.java

+10-11
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ static void assertError(Executable codeToRun, Class<? extends Throwable> expecte
9393
}
9494
}
9595

96-
96+
private final ObjectMapper MAPPER = newJsonMapper();
97+
9798
@Test
9899
public void testDeserialization01_zeroBased() throws Exception
99100
{
@@ -161,9 +162,9 @@ public void testFormatAnnotation_oneBased() throws Exception
161162
}
162163

163164
/*
164-
/**********************************************************
165+
/**********************************************************************
165166
/* Tests for empty string handling
166-
/**********************************************************
167+
/**********************************************************************
167168
*/
168169

169170
@Test
@@ -192,16 +193,14 @@ public void testDeserializeFromEmptyString() throws Exception
192193
}
193194

194195
private ObjectReader readerForZeroBased() {
195-
return JsonMapper.builder()
196-
.disable(DateTimeFeature.ONE_BASED_MONTHS)
197-
.build()
198-
.readerFor(Month.class);
196+
return MAPPER
197+
.readerFor(Month.class)
198+
.without(DateTimeFeature.ONE_BASED_MONTHS);
199199
}
200200

201201
private ObjectReader readerForOneBased() {
202-
return JsonMapper.builder()
203-
.enable(DateTimeFeature.ONE_BASED_MONTHS)
204-
.build()
205-
.readerFor(Month.class);
202+
return MAPPER
203+
.readerFor(Month.class)
204+
.with(DateTimeFeature.ONE_BASED_MONTHS);
206205
}
207206
}

0 commit comments

Comments
 (0)