Skip to content

Commit a39892c

Browse files
committed
Fix #2683
1 parent fa3ef3e commit a39892c

File tree

10 files changed

+200
-32
lines changed

10 files changed

+200
-32
lines changed

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Project: jackson-databind
88

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

1315
2.11.1 (not yet released)

src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java

+36-8
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public JsonDeserializer<Object> createBeanDeserializer(DeserializationContext ct
9393
throws JsonMappingException
9494
{
9595
final DeserializationConfig config = ctxt.getConfig();
96-
// We may also have custom overrides:
96+
// First: we may also have custom overrides:
9797
JsonDeserializer<?> deser = _findCustomBeanDeserializer(type, config, beanDesc);
9898
if (deser != null) {
9999
// [databind#2392]
@@ -104,10 +104,8 @@ public JsonDeserializer<Object> createBeanDeserializer(DeserializationContext ct
104104
}
105105
return (JsonDeserializer<Object>) deser;
106106
}
107-
/* One more thing to check: do we have an exception type
108-
* (Throwable or its sub-classes)? If so, need slightly
109-
* different handling.
110-
*/
107+
// One more thing to check: do we have an exception type (Throwable or its
108+
// sub-classes)? If so, need slightly different handling.
111109
if (type.isThrowable()) {
112110
return buildThrowableDeserializer(ctxt, type, beanDesc);
113111
}
@@ -139,6 +137,14 @@ public JsonDeserializer<Object> createBeanDeserializer(DeserializationContext ct
139137
}
140138
// For checks like [databind#1599]
141139
_validateSubType(ctxt, type, beanDesc);
140+
141+
// 05-May-2020, tatu: [databind#2683] Let's actually pre-emptively catch
142+
// certain types (for now, java.time.*) to give better error messages
143+
deser = _findUnsupportedTypeDeserializer(ctxt, type, beanDesc);
144+
if (deser != null) {
145+
return (JsonDeserializer<Object>)deser;
146+
}
147+
142148
// Use generic bean introspection to build deserializer
143149
return buildBeanDeserializer(ctxt, type, beanDesc);
144150
}
@@ -181,7 +187,30 @@ protected JsonDeserializer<?> findStdDeserializer(DeserializationContext ctxt,
181187
}
182188
return deser;
183189
}
184-
190+
191+
/**
192+
* Helper method called to see if given type, otherwise to be taken as POJO type,
193+
* is "known but not supported" JDK type, and if so, return alternate handler
194+
* (deserializer).
195+
* Initially added to support more meaningful error messages when "Java 8 date/time"
196+
* support module not registered.
197+
*
198+
* @since 2.12
199+
*/
200+
protected JsonDeserializer<Object> _findUnsupportedTypeDeserializer(DeserializationContext ctxt,
201+
JavaType type, BeanDescription beanDesc)
202+
throws JsonMappingException
203+
{
204+
if (ClassUtil.isJava8TimeClass(type.getRawClass())) {
205+
// 05-May-2020, tatu: Should we check for possible Shape override to "POJO"?
206+
// (to let users force 'serialize-as-POJO'?
207+
return new UnsupportedTypeDeserializer(type,
208+
"Java 8 date/time type "+ClassUtil.getTypeDescription(type)
209+
+" not supported by default: please register module `jackson-datatype-jsr310` to add handling");
210+
}
211+
return null;
212+
}
213+
185214
protected JavaType materializeAbstractType(DeserializationContext ctxt,
186215
JavaType type, BeanDescription beanDesc)
187216
throws JsonMappingException
@@ -434,8 +463,7 @@ public JsonDeserializer<Object> buildThrowableDeserializer(DeserializationContex
434463

435464
/*
436465
/**********************************************************
437-
/* Helper methods for Bean deserializer construction,
438-
/* overridable by sub-classes
466+
/* Helper methods for Bean deserializer construction
439467
/**********************************************************
440468
*/
441469

src/main/java/com/fasterxml/jackson/databind/deser/impl/FailingDeserializer.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44

55
import com.fasterxml.jackson.core.JsonParser;
66
import com.fasterxml.jackson.databind.DeserializationContext;
7-
import com.fasterxml.jackson.databind.JsonMappingException;
87
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
98

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

2221
public FailingDeserializer(String m) {
23-
super(Object.class);
22+
this(Object.class, m);
23+
}
24+
25+
// @since 2.12
26+
public FailingDeserializer(Class<?> rawType, String m) {
27+
super(rawType);
2428
_message = m;
2529
}
2630

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.fasterxml.jackson.databind.deser.impl;
2+
3+
import java.io.IOException;
4+
5+
import com.fasterxml.jackson.core.JsonParser;
6+
import com.fasterxml.jackson.databind.DeserializationContext;
7+
import com.fasterxml.jackson.databind.JavaType;
8+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
9+
10+
/**
11+
* Special bogus "serializer" that will throw
12+
* {@link com.fasterxml.jackson.databind.exc.MismatchedInputException}
13+
* if an attempt is made to deserialize a value.
14+
* This is used for "known unknown" types: types that we can recognize
15+
* but can not support easily (or support known to be added via extension
16+
* module).
17+
*
18+
* @since 2.12
19+
*/
20+
public class UnsupportedTypeDeserializer extends StdDeserializer<Object>
21+
{
22+
private static final long serialVersionUID = 1L;
23+
24+
protected final JavaType _type;
25+
26+
protected final String _message;
27+
28+
public UnsupportedTypeDeserializer(JavaType t, String m) {
29+
super(t);
30+
_type = t;
31+
_message = m;
32+
}
33+
34+
@Override
35+
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
36+
ctxt.reportBadDefinition(_type, _message);
37+
return null;
38+
}
39+
}

src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java

+25-3
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99

1010
import com.fasterxml.jackson.databind.*;
1111
import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
12+
import com.fasterxml.jackson.databind.deser.impl.UnsupportedTypeDeserializer;
1213
import com.fasterxml.jackson.databind.introspect.*;
1314
import com.fasterxml.jackson.databind.jsontype.NamedType;
1415
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
1516
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
1617
import com.fasterxml.jackson.databind.ser.impl.FilteredBeanPropertyWriter;
1718
import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
1819
import com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator;
20+
import com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer;
1921
import com.fasterxml.jackson.databind.ser.std.MapSerializer;
2022
import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer;
2123
import com.fasterxml.jackson.databind.type.ReferenceType;
@@ -376,6 +378,12 @@ protected JsonSerializer<Object> constructBeanOrAddOnSerializer(SerializerProvid
376378
return prov.getUnknownTypeSerializer(Object.class);
377379
// throw new IllegalArgumentException("Cannot create bean serializer for Object.class");
378380
}
381+
382+
JsonSerializer<?> ser = _findUnsupportedTypeSerializer(prov, type, beanDesc);
383+
if (ser != null) {
384+
return (JsonSerializer<Object>) ser;
385+
}
386+
379387
final SerializationConfig config = prov.getConfig();
380388
BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);
381389
builder.setConfig(config);
@@ -447,9 +455,8 @@ protected JsonSerializer<Object> constructBeanOrAddOnSerializer(SerializerProvid
447455
}
448456
}
449457

450-
JsonSerializer<Object> ser = null;
451458
try {
452-
ser = (JsonSerializer<Object>) builder.build();
459+
ser = builder.build();
453460
} catch (RuntimeException e) {
454461
return prov.reportBadTypeDefinition(beanDesc, "Failed to construct BeanSerializer for %s: (%s) %s",
455462
beanDesc.getType(), e.getClass().getName(), e.getMessage());
@@ -467,7 +474,7 @@ protected JsonSerializer<Object> constructBeanOrAddOnSerializer(SerializerProvid
467474
}
468475
}
469476
}
470-
return ser;
477+
return (JsonSerializer<Object>) ser;
471478
}
472479

473480
protected ObjectIdWriter constructObjectIdHandler(SerializerProvider prov,
@@ -814,4 +821,19 @@ protected BeanPropertyWriter _constructWriter(SerializerProvider prov,
814821
return pb.buildWriter(prov, propDef, type, annotatedSerializer,
815822
typeSer, contentTypeSer, accessor, staticTyping);
816823
}
824+
825+
protected JsonSerializer<?> _findUnsupportedTypeSerializer(SerializerProvider ctxt,
826+
JavaType type, BeanDescription beanDesc)
827+
throws JsonMappingException
828+
{
829+
if (ClassUtil.isJava8TimeClass(type.getRawClass())) {
830+
// 05-May-2020, tatu: Should we check for possible Shape override to "POJO"?
831+
// (to let users force 'serialize-as-POJO'?
832+
return new UnsupportedTypeSerializer(type,
833+
"Java 8 date/time type "+ClassUtil.getTypeDescription(type)
834+
+" not supported by default: please register module `jackson-datatype-jsr310` to add handling");
835+
}
836+
return null;
837+
838+
}
817839
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
package com.fasterxml.jackson.databind.ser.impl;
22

33
import java.io.IOException;
4-
import java.lang.reflect.Type;
54

65
import com.fasterxml.jackson.core.*;
7-
import com.fasterxml.jackson.databind.JavaType;
6+
87
import com.fasterxml.jackson.databind.JsonMappingException;
9-
import com.fasterxml.jackson.databind.JsonNode;
108
import com.fasterxml.jackson.databind.SerializerProvider;
11-
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
129
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
1310

1411
/**
@@ -30,19 +27,8 @@ public FailingSerializer(String msg) {
3027
}
3128

3229
@Override
33-
public void serialize(Object value, JsonGenerator g, SerializerProvider provider) throws IOException
34-
{
35-
provider.reportMappingProblem(_msg);
36-
}
37-
38-
@Override
39-
public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
40-
return null;
41-
}
42-
43-
@Override
44-
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
30+
public void serialize(Object value, JsonGenerator g, SerializerProvider ctxt) throws IOException
4531
{
46-
;
32+
ctxt.reportMappingProblem(_msg);
4733
}
4834
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.fasterxml.jackson.databind.ser.impl;
2+
3+
import java.io.IOException;
4+
5+
import com.fasterxml.jackson.core.*;
6+
7+
import com.fasterxml.jackson.databind.JavaType;
8+
import com.fasterxml.jackson.databind.SerializerProvider;
9+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
10+
11+
/**
12+
* Special bogus "serializer" that will throw
13+
* {@link com.fasterxml.jackson.databind.exc.InvalidDefinitionException} if its {@link #serialize}
14+
* gets invoked. Most commonly registered as handler for unknown types,
15+
* as well as for catching unintended usage (like trying to use null
16+
* as Map/Object key).
17+
*/
18+
public class UnsupportedTypeSerializer
19+
extends StdSerializer<Object>
20+
{
21+
private static final long serialVersionUID = 1L;
22+
23+
protected final JavaType _type;
24+
25+
protected final String _message;
26+
27+
public UnsupportedTypeSerializer(JavaType t, String msg) {
28+
super(Object.class);
29+
_type = t;
30+
_message = msg;
31+
}
32+
33+
@Override
34+
public void serialize(Object value, JsonGenerator g, SerializerProvider ctxt) throws IOException {
35+
ctxt.reportBadDefinition(_type, _message);
36+
}
37+
}

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

+7
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,13 @@ public static boolean isJDKClass(Class<?> rawType) {
10841084
return rawType.getName().startsWith("java.");
10851085
}
10861086

1087+
/**
1088+
* @since 2.12
1089+
*/
1090+
public static boolean isJava8TimeClass(Class<?> rawType) {
1091+
return rawType.getName().startsWith("java.time.");
1092+
}
1093+
10871094
/*
10881095
/**********************************************************
10891096
/* Access to various Class definition aspects; possibly
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.fasterxml.jackson.databind.deser.jdk;
2+
3+
import java.time.Instant;
4+
import java.time.OffsetDateTime;
5+
import java.time.ZoneOffset;
6+
7+
import com.fasterxml.jackson.databind.*;
8+
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
9+
10+
// [databind#2683]: add fallback handling for Java 8 date/time types, to
11+
// prevent accidental serialzization as POJOs, as well as give more information
12+
// on deserialization attempts
13+
public class DateJava8FallbacksTest extends BaseMapTest
14+
{
15+
private final ObjectMapper MAPPER = newJsonMapper();
16+
17+
private final OffsetDateTime DATETIME_EPOCH = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L),
18+
ZoneOffset.of("Z"));
19+
20+
// Test to prevent serialization as POJO, without Java 8 date/time module:
21+
public void testPreventSerialization() throws Exception
22+
{
23+
try {
24+
String json = MAPPER.writerWithDefaultPrettyPrinter()
25+
.writeValueAsString(DATETIME_EPOCH);
26+
fail("Should not pass, wrote out as\n: "+json);
27+
} catch (InvalidDefinitionException e) {
28+
verifyException(e, "Java 8 date/time type `java.time.OffsetDateTime` not supported by default");
29+
verifyException(e, "please register module `jackson-datatype-jsr310`");
30+
}
31+
}
32+
33+
public void testBetterDeserializationError() throws Exception
34+
{
35+
try {
36+
OffsetDateTime result = MAPPER.readValue(" 0 ", OffsetDateTime.class);
37+
fail("Not expecting to pass, resulted in: "+result);
38+
} catch (InvalidDefinitionException e) {
39+
verifyException(e, "Java 8 date/time type `java.time.OffsetDateTime` not supported by default");
40+
verifyException(e, "please register module `jackson-datatype-jsr310`");
41+
}
42+
}
43+
}

src/test/java/com/fasterxml/jackson/databind/jsontype/TestWithGenerics.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public ContainerWithTwoAnimals(U a1, V a2) {
126126
otherAnimal = a2;
127127
}
128128
}
129-
129+
130130
/*
131131
/**********************************************************
132132
/* Unit tests

0 commit comments

Comments
 (0)