Skip to content

Commit 5d504ca

Browse files
committed
Fix #4082: add check for attempts to ser/deser Java 8 optionals without module
1 parent 45e6fa6 commit 5d504ca

File tree

3 files changed

+108
-0
lines changed

3 files changed

+108
-0
lines changed

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ Project: jackson-databind
6363
#4078: `java.desktop` module is no longer optional
6464
(reported by Andreas Z)
6565
(fix contributed by Joo-Hyuk K)
66+
#4082: `ClassUtil` fails with `java.lang.reflect.InaccessibleObjectException`
67+
trying to setAccessible on `OptionalInt` with JDK 17+
6668

6769
2.15.3 (not yet released)
6870

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,9 @@ public static String checkUnsupportedType(JavaType type) {
309309
} else if (isJodaTimeClass(className)) {
310310
typeName = "Joda date/time";
311311
moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-joda";
312+
} else if (isJava8OptionalClass(className)) {
313+
typeName = "Java 8 optional";
314+
moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-jdk8";
312315
} else {
313316
return null;
314317
}
@@ -323,17 +326,31 @@ public static boolean isJava8TimeClass(Class<?> rawType) {
323326
return isJava8TimeClass(rawType.getName());
324327
}
325328

329+
// @since 2.12
326330
private static boolean isJava8TimeClass(String className) {
327331
return className.startsWith("java.time.");
328332
}
329333

334+
/**
335+
* @since 2.16
336+
*/
337+
public static boolean isJava8OptionalClass(Class<?> rawType) {
338+
return isJava8OptionalClass(rawType.getName());
339+
}
340+
341+
// @since 2.16
342+
private static boolean isJava8OptionalClass(String className) {
343+
return className.startsWith("java.util.Optional");
344+
}
345+
330346
/**
331347
* @since 2.12
332348
*/
333349
public static boolean isJodaTimeClass(Class<?> rawType) {
334350
return isJodaTimeClass(rawType.getName());
335351
}
336352

353+
// @since 2.12
337354
private static boolean isJodaTimeClass(String className) {
338355
return className.startsWith("org.joda.time.");
339356
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.fasterxml.jackson.databind.interop;
2+
3+
import java.util.Optional;
4+
import java.util.OptionalDouble;
5+
import java.util.OptionalInt;
6+
import java.util.OptionalLong;
7+
8+
import com.fasterxml.jackson.core.*;
9+
10+
import com.fasterxml.jackson.databind.*;
11+
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
12+
import com.fasterxml.jackson.databind.util.TokenBuffer;
13+
14+
// [databind#4082]: add fallback handling for Java 8 Optional types, to
15+
// prevent accidental serialization as POJOs, as well as give more information
16+
// on deserialization attempts
17+
//
18+
// @since 2.16
19+
public class OptionalJava8Fallbacks4082Test extends BaseMapTest
20+
{
21+
private final ObjectMapper MAPPER = newJsonMapper();
22+
23+
// Test to prevent serialization as POJO, without Java 8 date/time module:
24+
public void testPreventSerialization() throws Exception {
25+
_testPreventSerialization(Optional.empty());
26+
_testPreventSerialization(OptionalInt.of(13));
27+
_testPreventSerialization(OptionalLong.of(-1L));
28+
_testPreventSerialization(OptionalDouble.of(0.5));
29+
}
30+
31+
private void _testPreventSerialization(Object value) throws Exception
32+
{
33+
try {
34+
String json = MAPPER.writeValueAsString(value);
35+
fail("Should not pass, wrote out as\n: "+json);
36+
} catch (InvalidDefinitionException e) {
37+
verifyException(e, "Java 8 optional type `"+value.getClass().getName()
38+
+"` not supported by default");
39+
verifyException(e, "add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jdk8\"");
40+
}
41+
}
42+
43+
public void testBetterDeserializationError() throws Exception
44+
{
45+
_testBetterDeserializationError(Optional.class);
46+
_testBetterDeserializationError(OptionalInt.class);
47+
_testBetterDeserializationError(OptionalLong.class);
48+
_testBetterDeserializationError(OptionalDouble.class);
49+
}
50+
51+
private void _testBetterDeserializationError(Class<?> target) throws Exception
52+
{
53+
try {
54+
Object result = MAPPER.readValue(" 0 ", target);
55+
fail("Not expecting to pass, resulted in: "+result);
56+
} catch (InvalidDefinitionException e) {
57+
verifyException(e, "Java 8 optional type `"+target.getName()+"` not supported by default");
58+
verifyException(e, "add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jdk8\"");
59+
}
60+
}
61+
62+
// But, [databind#3091], allow deser from JsonToken.VALUE_EMBEDDED_OBJECT
63+
public void testAllowAsEmbedded() throws Exception
64+
{
65+
Optional<Object> optValue = Optional.empty();
66+
try (TokenBuffer tb = new TokenBuffer((ObjectCodec) null, false)) {
67+
tb.writeEmbeddedObject(optValue);
68+
69+
try (JsonParser p = tb.asParser()) {
70+
Optional<?> result = MAPPER.readValue(p, Optional.class);
71+
assertSame(optValue, result);
72+
}
73+
}
74+
75+
// but also try deser into an array
76+
try (TokenBuffer tb = new TokenBuffer((ObjectCodec) null, false)) {
77+
tb.writeStartArray();
78+
tb.writeEmbeddedObject(optValue);
79+
tb.writeEndArray();
80+
81+
try (JsonParser p = tb.asParser()) {
82+
Object[] result = MAPPER.readValue(p, Object[].class);
83+
assertNotNull(result);
84+
assertEquals(1, result.length);
85+
assertSame(optValue, result[0]);
86+
}
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)