Skip to content

Commit 707c3c1

Browse files
authored
Fix #4533, add MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES (#5105)
1 parent 21a11f4 commit 707c3c1

File tree

5 files changed

+149
-2
lines changed

5 files changed

+149
-2
lines changed

release-notes/CREDITS-2.x

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ Michal Letynski (mletynski@github)
321321
Jeff Schnitzer (stickfigure@github)
322322
* Suggested #504: Add `DeserializationFeature.USE_LONG_FOR_INTS`
323323
(2.6.0)
324+
* Requested #4533: Add `MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES` to disable
325+
the "Java 8 date/time XYZ not supported by default" error
326+
(2.19.0)
324327

325328
Jerry Yang (islanderman@github)
326329
* Contributed #820: Add new method for `ObjectReader`, to bind from JSON Pointer position

release-notes/VERSION-2.x

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ Project: jackson-databind
2222
(requested by @alzimmermsft)
2323
#4388: Allow using `@JsonPropertyOrder` with "any" (`@JsonAnyGetter`) properties
2424
(fix by Joo-Hyuk K)
25+
#4533: Add `MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES` to disable the
26+
"Java 8 date/time XYZ not supported by default" error
27+
(requested by Jeff S)
28+
(fix by Joo-Hyuk K)
2529
#4650: `PrimitiveArrayDeserializers` should deal with single String value if
2630
`DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY` enabled
2731
(reported, fix suggested by @eeren-bm)

src/main/java/com/fasterxml/jackson/databind/MapperFeature.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,21 @@ public enum MapperFeature implements ConfigFeature
661661
*
662662
* @since 2.19
663663
*/
664-
REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS(true)
664+
REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS(true),
665+
666+
/**
667+
* Feature that determines what happens if Java 8 {@link java.time} (and
668+
* other related Java 8 date/time types) are to be serialized or deserialized, but there
669+
* are no registered handlers for them.
670+
* If enabled, an exception is thrown (to indicate problem, a solution for which is
671+
* to register {@code jackson-datatype-jsr310} module); if disabled, the value is
672+
* serialized and/or deserialized using regular POJO ("Bean") (de)serialization.
673+
*<p>
674+
* Feature is enabled by default.
675+
*
676+
* @since 2.19
677+
*/
678+
REQUIRE_HANDLERS_FOR_JAVA8_TIMES(true),
665679
;
666680

667681
private final boolean _defaultState;

src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@ public static String stdManglePropertyName(final String basename, final int offs
295295
* "well-known" types for which there would be a datatype module; and if so,
296296
* return appropriate failure message to give to caller.
297297
*
298+
* @return error message to use, or null if failure is not needed.
299+
*
298300
* @since 2.19
299301
*/
300302
public static String checkUnsupportedType(MapperConfig<?> config, JavaType type) {
@@ -312,6 +314,11 @@ public static String checkUnsupportedType(MapperConfig<?> config, JavaType type)
312314
if (type.isTypeOrSubTypeOf(Throwable.class)) {
313315
return null;
314316
}
317+
failFeature = MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES;
318+
final boolean fail = (config == null) || config.isEnabled(failFeature);
319+
if (!fail) {
320+
return null;
321+
}
315322
typeName = "Java 8 date/time";
316323
moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310";
317324
} else if (isJodaTimeClass(className)) {
@@ -332,7 +339,7 @@ public static String checkUnsupportedType(MapperConfig<?> config, JavaType type)
332339
typeName, ClassUtil.getTypeDescription(type), moduleName);
333340
if (failFeature != null) {
334341
str = String.format("%s (or disable `MapperFeature.%s`)",
335-
str, MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS.name());
342+
str, failFeature.name());
336343
}
337344
return str;
338345
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.fasterxml.jackson.databind.interop;
2+
3+
import java.time.LocalDate;
4+
5+
import org.assertj.core.api.Assertions;
6+
import org.junit.jupiter.api.Test;
7+
8+
import com.fasterxml.jackson.core.JacksonException;
9+
import com.fasterxml.jackson.databind.*;
10+
import com.fasterxml.jackson.databind.json.JsonMapper;
11+
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
12+
13+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
14+
import static org.junit.jupiter.api.Assertions.assertThrows;
15+
import static org.junit.jupiter.api.Assertions.fail;
16+
17+
public class Java8Datetime4533Test
18+
extends DatabindTestUtil
19+
{
20+
private final ObjectMapper MAPPER = newJsonMapper();
21+
22+
private final ObjectMapper LENIENT_MAPPER = JsonMapper.builder()
23+
.disable(MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES)
24+
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
25+
.build();
26+
27+
@Test
28+
public void testPreventSerialization() throws Exception
29+
{
30+
// [databind#4533]: prevent accidental serialization of Java 8 date/time types
31+
// as POJOs, without Java 8 date/time module:
32+
_testPreventSerialization(java.time.LocalDateTime.now());
33+
_testPreventSerialization(java.time.LocalDate.now());
34+
_testPreventSerialization(java.time.LocalTime.now());
35+
_testPreventSerialization(java.time.OffsetDateTime.now());
36+
_testPreventSerialization(java.time.ZonedDateTime.now());
37+
}
38+
39+
private void _testPreventSerialization(Object value) throws Exception
40+
{
41+
try {
42+
String json = MAPPER.writeValueAsString(value);
43+
fail("Should not pass, wrote out as\n: "+json);
44+
} catch (com.fasterxml.jackson.databind.exc.InvalidDefinitionException e) {
45+
verifyException(e, "Java 8 date/time type `"+value.getClass().getName()
46+
+"` not supported by default");
47+
verifyException(e, "add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\"");
48+
verifyException(e, "(or disable `MapperFeature.%s`)",
49+
MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES.name());
50+
}
51+
}
52+
53+
@Test
54+
public void testPreventDeserialization() throws Exception
55+
{
56+
// [databind#4533]: prevent accidental deserialization of Java 8 date/time types
57+
// as POJOs, without Java 8 date/time module:
58+
_testPreventDeserialization(java.time.LocalDateTime.class);
59+
_testPreventDeserialization(java.time.LocalDate.class);
60+
_testPreventDeserialization(java.time.LocalTime.class);
61+
_testPreventDeserialization(java.time.OffsetDateTime.class);
62+
_testPreventDeserialization(java.time.ZonedDateTime.class);
63+
}
64+
65+
private void _testPreventDeserialization(Class<?> value) throws Exception
66+
{
67+
try {
68+
Object result = MAPPER.readValue(" 0 ", value);
69+
fail("Not expecting to pass, resulted in: "+result);
70+
} catch (com.fasterxml.jackson.databind.exc.InvalidDefinitionException e) {
71+
verifyException(e, "Java 8 date/time type `"+value.getName()
72+
+"` not supported by default");
73+
verifyException(e, "add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\"");
74+
verifyException(e, "(or disable `MapperFeature.%s`)",
75+
MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES.name());
76+
}
77+
}
78+
79+
@Test
80+
public void testLenientSerailization() throws Exception
81+
{
82+
_testLenientSerialization(java.time.LocalDateTime.now());
83+
_testLenientSerialization(java.time.LocalDate.now());
84+
_testLenientSerialization(java.time.LocalTime.now());
85+
_testLenientSerialization(java.time.OffsetDateTime.now());
86+
87+
// Except ZonedDateTime serialization fails with ...
88+
try {
89+
_testLenientSerialization(java.time.ZonedDateTime.now());
90+
fail("Should not pass, wrote out as\n: "+java.time.ZonedDateTime.now());
91+
} catch (JsonMappingException e) {
92+
verifyException(e, "Class com.fasterxml.jackson.databind.ser.BeanPropertyWriter");
93+
verifyException(e, "with modifiers \"public\"");
94+
}
95+
}
96+
97+
private void _testLenientSerialization(Object value) throws Exception
98+
{
99+
String json = LENIENT_MAPPER.writeValueAsString(value);
100+
assertThat(json).isNotNull();
101+
}
102+
103+
@Test
104+
public void testAllowDeserializationWithFeature() throws Exception
105+
{
106+
_testAllowDeserializationLenient(java.time.LocalDateTime.class);
107+
_testAllowDeserializationLenient(java.time.LocalDate.class);
108+
_testAllowDeserializationLenient(java.time.LocalTime.class);
109+
_testAllowDeserializationLenient(java.time.OffsetDateTime.class);
110+
_testAllowDeserializationLenient(java.time.ZonedDateTime.class);
111+
}
112+
113+
private void _testAllowDeserializationLenient(Class<?> target) throws Exception {
114+
JacksonException e = assertThrows(JacksonException.class, () ->
115+
LENIENT_MAPPER.readValue("{}", target));
116+
Assertions.assertThat(e).hasMessageContaining("Cannot construct instance of `"+target.getName()+"`");
117+
}
118+
119+
}

0 commit comments

Comments
 (0)