Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #4082: add check for attempts to ser/deser Java 8 optionals without module registered #4087

Merged
merged 1 commit into from
Aug 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Project: jackson-databind
#4078: `java.desktop` module is no longer optional
(reported by Andreas Z)
(fix contributed by Joo-Hyuk K)
#4082: `ClassUtil` fails with `java.lang.reflect.InaccessibleObjectException`
trying to setAccessible on `OptionalInt` with JDK 17+

2.15.3 (not yet released)

Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ public static String checkUnsupportedType(JavaType type) {
} else if (isJodaTimeClass(className)) {
typeName = "Joda date/time";
moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-joda";
} else if (isJava8OptionalClass(className)) {
typeName = "Java 8 optional";
moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-jdk8";
} else {
return null;
}
Expand All @@ -323,17 +326,31 @@ public static boolean isJava8TimeClass(Class<?> rawType) {
return isJava8TimeClass(rawType.getName());
}

// @since 2.12
private static boolean isJava8TimeClass(String className) {
return className.startsWith("java.time.");
}

/**
* @since 2.16
*/
public static boolean isJava8OptionalClass(Class<?> rawType) {
return isJava8OptionalClass(rawType.getName());
}

// @since 2.16
private static boolean isJava8OptionalClass(String className) {
return className.startsWith("java.util.Optional");
}

/**
* @since 2.12
*/
public static boolean isJodaTimeClass(Class<?> rawType) {
return isJodaTimeClass(rawType.getName());
}

// @since 2.12
private static boolean isJodaTimeClass(String className) {
return className.startsWith("org.joda.time.");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.fasterxml.jackson.databind.interop;

import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;

import com.fasterxml.jackson.core.*;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.util.TokenBuffer;

// [databind#4082]: add fallback handling for Java 8 Optional types, to
// prevent accidental serialization as POJOs, as well as give more information
// on deserialization attempts
//
// @since 2.16
public class OptionalJava8Fallbacks4082Test extends BaseMapTest
{
private final ObjectMapper MAPPER = newJsonMapper();

// Test to prevent serialization as POJO, without Java 8 date/time module:
public void testPreventSerialization() throws Exception {
_testPreventSerialization(Optional.empty());
_testPreventSerialization(OptionalInt.of(13));
_testPreventSerialization(OptionalLong.of(-1L));
_testPreventSerialization(OptionalDouble.of(0.5));
}

private void _testPreventSerialization(Object value) throws Exception
{
try {
String json = MAPPER.writeValueAsString(value);
fail("Should not pass, wrote out as\n: "+json);
} catch (InvalidDefinitionException e) {
verifyException(e, "Java 8 optional type `"+value.getClass().getName()
+"` not supported by default");
verifyException(e, "add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jdk8\"");
}
}

public void testBetterDeserializationError() throws Exception
{
_testBetterDeserializationError(Optional.class);
_testBetterDeserializationError(OptionalInt.class);
_testBetterDeserializationError(OptionalLong.class);
_testBetterDeserializationError(OptionalDouble.class);
}

private void _testBetterDeserializationError(Class<?> target) throws Exception
{
try {
Object result = MAPPER.readValue(" 0 ", target);
fail("Not expecting to pass, resulted in: "+result);
} catch (InvalidDefinitionException e) {
verifyException(e, "Java 8 optional type `"+target.getName()+"` not supported by default");
verifyException(e, "add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jdk8\"");
}
}

// But, [databind#3091], allow deser from JsonToken.VALUE_EMBEDDED_OBJECT
public void testAllowAsEmbedded() throws Exception
{
Optional<Object> optValue = Optional.empty();
try (TokenBuffer tb = new TokenBuffer((ObjectCodec) null, false)) {
tb.writeEmbeddedObject(optValue);

try (JsonParser p = tb.asParser()) {
Optional<?> result = MAPPER.readValue(p, Optional.class);
assertSame(optValue, result);
}
}

// but also try deser into an array
try (TokenBuffer tb = new TokenBuffer((ObjectCodec) null, false)) {
tb.writeStartArray();
tb.writeEmbeddedObject(optValue);
tb.writeEndArray();

try (JsonParser p = tb.asParser()) {
Object[] result = MAPPER.readValue(p, Object[].class);
assertNotNull(result);
assertEquals(1, result.length);
assertSame(optValue, result[0]);
}
}
}
}