Skip to content

Commit

Permalink
Fix #2683
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed May 6, 2020
1 parent fa3ef3e commit a39892c
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 32 deletions.
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Project: jackson-databind

#792: Deserialization Not Working Right with Generic Types and Builders
(reported by Mike G; fix contributed by Ville K)
#2683: Explicitly fail (de)serialization of `java.time.*` types in absence of
registered custom (de)serializers
#2707: Improve description included in by `DeserializationContext.handleUnexpectedToken()`

2.11.1 (not yet released)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public JsonDeserializer<Object> createBeanDeserializer(DeserializationContext ct
throws JsonMappingException
{
final DeserializationConfig config = ctxt.getConfig();
// We may also have custom overrides:
// First: we may also have custom overrides:
JsonDeserializer<?> deser = _findCustomBeanDeserializer(type, config, beanDesc);
if (deser != null) {
// [databind#2392]
Expand All @@ -104,10 +104,8 @@ public JsonDeserializer<Object> createBeanDeserializer(DeserializationContext ct
}
return (JsonDeserializer<Object>) deser;
}
/* One more thing to check: do we have an exception type
* (Throwable or its sub-classes)? If so, need slightly
* different handling.
*/
// One more thing to check: do we have an exception type (Throwable or its
// sub-classes)? If so, need slightly different handling.
if (type.isThrowable()) {
return buildThrowableDeserializer(ctxt, type, beanDesc);
}
Expand Down Expand Up @@ -139,6 +137,14 @@ public JsonDeserializer<Object> createBeanDeserializer(DeserializationContext ct
}
// For checks like [databind#1599]
_validateSubType(ctxt, type, beanDesc);

// 05-May-2020, tatu: [databind#2683] Let's actually pre-emptively catch
// certain types (for now, java.time.*) to give better error messages
deser = _findUnsupportedTypeDeserializer(ctxt, type, beanDesc);
if (deser != null) {
return (JsonDeserializer<Object>)deser;
}

// Use generic bean introspection to build deserializer
return buildBeanDeserializer(ctxt, type, beanDesc);
}
Expand Down Expand Up @@ -181,7 +187,30 @@ protected JsonDeserializer<?> findStdDeserializer(DeserializationContext ctxt,
}
return deser;
}


/**
* Helper method called to see if given type, otherwise to be taken as POJO type,
* is "known but not supported" JDK type, and if so, return alternate handler
* (deserializer).
* Initially added to support more meaningful error messages when "Java 8 date/time"
* support module not registered.
*
* @since 2.12
*/
protected JsonDeserializer<Object> _findUnsupportedTypeDeserializer(DeserializationContext ctxt,
JavaType type, BeanDescription beanDesc)
throws JsonMappingException
{
if (ClassUtil.isJava8TimeClass(type.getRawClass())) {
// 05-May-2020, tatu: Should we check for possible Shape override to "POJO"?
// (to let users force 'serialize-as-POJO'?
return new UnsupportedTypeDeserializer(type,
"Java 8 date/time type "+ClassUtil.getTypeDescription(type)
+" not supported by default: please register module `jackson-datatype-jsr310` to add handling");
}
return null;
}

protected JavaType materializeAbstractType(DeserializationContext ctxt,
JavaType type, BeanDescription beanDesc)
throws JsonMappingException
Expand Down Expand Up @@ -434,8 +463,7 @@ public JsonDeserializer<Object> buildThrowableDeserializer(DeserializationContex

/*
/**********************************************************
/* Helper methods for Bean deserializer construction,
/* overridable by sub-classes
/* Helper methods for Bean deserializer construction
/**********************************************************
*/

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

/**
* Special bogus "serializer" that will throw
* {@link JsonMappingException} if an attempt is made to deserialize
* {@link com.fasterxml.jackson.databind.exc.MismatchedInputException} if an attempt is made to deserialize
* a value. This is used as placeholder to avoid NPEs for uninitialized
* structured serializers or handlers.
*/
Expand All @@ -20,7 +19,12 @@ public class FailingDeserializer extends StdDeserializer<Object>
protected final String _message;

public FailingDeserializer(String m) {
super(Object.class);
this(Object.class, m);
}

// @since 2.12
public FailingDeserializer(Class<?> rawType, String m) {
super(rawType);
_message = m;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.fasterxml.jackson.databind.deser.impl;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

/**
* Special bogus "serializer" that will throw
* {@link com.fasterxml.jackson.databind.exc.MismatchedInputException}
* if an attempt is made to deserialize a value.
* This is used for "known unknown" types: types that we can recognize
* but can not support easily (or support known to be added via extension
* module).
*
* @since 2.12
*/
public class UnsupportedTypeDeserializer extends StdDeserializer<Object>
{
private static final long serialVersionUID = 1L;

protected final JavaType _type;

protected final String _message;

public UnsupportedTypeDeserializer(JavaType t, String m) {
super(t);
_type = t;
_message = m;
}

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ctxt.reportBadDefinition(_type, _message);
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
import com.fasterxml.jackson.databind.deser.impl.UnsupportedTypeDeserializer;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.impl.FilteredBeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
import com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator;
import com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer;
import com.fasterxml.jackson.databind.ser.std.MapSerializer;
import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer;
import com.fasterxml.jackson.databind.type.ReferenceType;
Expand Down Expand Up @@ -376,6 +378,12 @@ protected JsonSerializer<Object> constructBeanOrAddOnSerializer(SerializerProvid
return prov.getUnknownTypeSerializer(Object.class);
// throw new IllegalArgumentException("Cannot create bean serializer for Object.class");
}

JsonSerializer<?> ser = _findUnsupportedTypeSerializer(prov, type, beanDesc);
if (ser != null) {
return (JsonSerializer<Object>) ser;
}

final SerializationConfig config = prov.getConfig();
BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);
builder.setConfig(config);
Expand Down Expand Up @@ -447,9 +455,8 @@ protected JsonSerializer<Object> constructBeanOrAddOnSerializer(SerializerProvid
}
}

JsonSerializer<Object> ser = null;
try {
ser = (JsonSerializer<Object>) builder.build();
ser = builder.build();
} catch (RuntimeException e) {
return prov.reportBadTypeDefinition(beanDesc, "Failed to construct BeanSerializer for %s: (%s) %s",
beanDesc.getType(), e.getClass().getName(), e.getMessage());
Expand All @@ -467,7 +474,7 @@ protected JsonSerializer<Object> constructBeanOrAddOnSerializer(SerializerProvid
}
}
}
return ser;
return (JsonSerializer<Object>) ser;
}

protected ObjectIdWriter constructObjectIdHandler(SerializerProvider prov,
Expand Down Expand Up @@ -814,4 +821,19 @@ protected BeanPropertyWriter _constructWriter(SerializerProvider prov,
return pb.buildWriter(prov, propDef, type, annotatedSerializer,
typeSer, contentTypeSer, accessor, staticTyping);
}

protected JsonSerializer<?> _findUnsupportedTypeSerializer(SerializerProvider ctxt,
JavaType type, BeanDescription beanDesc)
throws JsonMappingException
{
if (ClassUtil.isJava8TimeClass(type.getRawClass())) {
// 05-May-2020, tatu: Should we check for possible Shape override to "POJO"?
// (to let users force 'serialize-as-POJO'?
return new UnsupportedTypeSerializer(type,
"Java 8 date/time type "+ClassUtil.getTypeDescription(type)
+" not supported by default: please register module `jackson-datatype-jsr310` to add handling");
}
return null;

}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package com.fasterxml.jackson.databind.ser.impl;

import java.io.IOException;
import java.lang.reflect.Type;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.JavaType;

import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

/**
Expand All @@ -30,19 +27,8 @@ public FailingSerializer(String msg) {
}

@Override
public void serialize(Object value, JsonGenerator g, SerializerProvider provider) throws IOException
{
provider.reportMappingProblem(_msg);
}

@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
return null;
}

@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
public void serialize(Object value, JsonGenerator g, SerializerProvider ctxt) throws IOException
{
;
ctxt.reportMappingProblem(_msg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.fasterxml.jackson.databind.ser.impl;

import java.io.IOException;

import com.fasterxml.jackson.core.*;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

/**
* Special bogus "serializer" that will throw
* {@link com.fasterxml.jackson.databind.exc.InvalidDefinitionException} if its {@link #serialize}
* gets invoked. Most commonly registered as handler for unknown types,
* as well as for catching unintended usage (like trying to use null
* as Map/Object key).
*/
public class UnsupportedTypeSerializer
extends StdSerializer<Object>
{
private static final long serialVersionUID = 1L;

protected final JavaType _type;

protected final String _message;

public UnsupportedTypeSerializer(JavaType t, String msg) {
super(Object.class);
_type = t;
_message = msg;
}

@Override
public void serialize(Object value, JsonGenerator g, SerializerProvider ctxt) throws IOException {
ctxt.reportBadDefinition(_type, _message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,13 @@ public static boolean isJDKClass(Class<?> rawType) {
return rawType.getName().startsWith("java.");
}

/**
* @since 2.12
*/
public static boolean isJava8TimeClass(Class<?> rawType) {
return rawType.getName().startsWith("java.time.");
}

/*
/**********************************************************
/* Access to various Class definition aspects; possibly
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.fasterxml.jackson.databind.deser.jdk;

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

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

// [databind#2683]: add fallback handling for Java 8 date/time types, to
// prevent accidental serialzization as POJOs, as well as give more information
// on deserialization attempts
public class DateJava8FallbacksTest extends BaseMapTest
{
private final ObjectMapper MAPPER = newJsonMapper();

private final OffsetDateTime DATETIME_EPOCH = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L),
ZoneOffset.of("Z"));

// Test to prevent serialization as POJO, without Java 8 date/time module:
public void testPreventSerialization() throws Exception
{
try {
String json = MAPPER.writerWithDefaultPrettyPrinter()
.writeValueAsString(DATETIME_EPOCH);
fail("Should not pass, wrote out as\n: "+json);
} catch (InvalidDefinitionException e) {
verifyException(e, "Java 8 date/time type `java.time.OffsetDateTime` not supported by default");
verifyException(e, "please register module `jackson-datatype-jsr310`");
}
}

public void testBetterDeserializationError() throws Exception
{
try {
OffsetDateTime result = MAPPER.readValue(" 0 ", OffsetDateTime.class);
fail("Not expecting to pass, resulted in: "+result);
} catch (InvalidDefinitionException e) {
verifyException(e, "Java 8 date/time type `java.time.OffsetDateTime` not supported by default");
verifyException(e, "please register module `jackson-datatype-jsr310`");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public ContainerWithTwoAnimals(U a1, V a2) {
otherAnimal = a2;
}
}

/*
/**********************************************************
/* Unit tests
Expand Down

0 comments on commit a39892c

Please sign in to comment.