diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
index ab64e26e29..ecbda48b81 100644
--- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
+++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
@@ -198,6 +198,18 @@ public enum MapperFeature implements ConfigFeature
*/
USE_STATIC_TYPING(false),
+ /**
+ * Feature that enables inferring builder type bindings from the value type
+ * being deserialized. This requires that the generic type declaration on
+ * the value type match that on the builder exactly.
+ *
+ * Feature is disabled by default which means that deserialization does
+ * not support deserializing types via builders with type parameters.
+ *
+ * See: https://github.com/FasterXML/jackson-databind/issues/921
+ */
+ INFER_BUILDER_TYPE_BINDINGS(false),
+
/*
/******************************************************
/* View-related features
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
index d21932bf9c..0afab68d9a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
@@ -138,12 +138,18 @@ public JsonDeserializer createBeanDeserializer(DeserializationContext ct
}
@Override
- public JsonDeserializer createBuilderBasedDeserializer(DeserializationContext ctxt,
- JavaType valueType, BeanDescription beanDesc, Class> builderClass)
- throws JsonMappingException
+ public JsonDeserializer createBuilderBasedDeserializer(
+ DeserializationContext ctxt, JavaType valueType, BeanDescription beanDesc,
+ Class> builderClass)
+ throws JsonMappingException
{
// First: need a BeanDescription for builder class
- JavaType builderType = ctxt.constructType(builderClass);
+ JavaType builderType;
+ if (ctxt.isEnabled(MapperFeature.INFER_BUILDER_TYPE_BINDINGS)) {
+ builderType = ctxt.getTypeFactory().constructParametricType(builderClass, valueType.getBindings());
+ } else {
+ builderType = ctxt.constructType(builderClass);
+ }
BeanDescription builderDesc = ctxt.getConfig().introspectForBuilder(builderType);
return buildBuilderBasedDeserializer(ctxt, valueType, builderDesc);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java
index 766cb69110..e65eee006a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java
@@ -894,7 +894,29 @@ public JavaType constructParametricType(Class> parametrized, Class>... param
*/
public JavaType constructParametricType(Class> rawType, JavaType... parameterTypes)
{
- return _fromClass(null, rawType, TypeBindings.create(rawType, parameterTypes));
+ return constructParametricType(rawType, TypeBindings.create(rawType, parameterTypes));
+ }
+
+ /**
+ * Factory method for constructing {@link JavaType} that
+ * represents a parameterized type. The type's parameters are
+ * specified as an instance of {@link TypeBindings}. This
+ * is useful if you already have the type's parameters such
+ * as those found on {@link JavaType}. For example, you could
+ * call
+ *
+ * return TypeFactory.constructParametricType(ArrayList.class, javaType.getBindings());
+ *
+ * This effectively applies the parameterized types from one
+ * {@link JavaType} to another class.
+ *
+ * @param rawType Actual type-erased type
+ * @param parameterTypes Type bindings for the raw type
+ * @since 3.0
+ */
+ public JavaType constructParametricType(Class> rawType, TypeBindings parameterTypes)
+ {
+ return _fromClass(null, rawType, parameterTypes);
}
/*
diff --git a/src/test/java/com/fasterxml/jackson/failing/BuilderDeserializationTest921.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithTypeParametersTest.java
similarity index 70%
rename from src/test/java/com/fasterxml/jackson/failing/BuilderDeserializationTest921.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithTypeParametersTest.java
index 79beaee6f5..d8dcfa334a 100644
--- a/src/test/java/com/fasterxml/jackson/failing/BuilderDeserializationTest921.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithTypeParametersTest.java
@@ -1,13 +1,16 @@
-package com.fasterxml.jackson.failing;
+package com.fasterxml.jackson.databind.deser.builder;
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import java.util.LinkedHashMap;
+import java.util.List;
-public class BuilderDeserializationTest921
+public class BuilderWithTypeParametersTest
extends BaseMapTest
{
public static class MyPOJO {
@@ -77,7 +80,19 @@ public MyGenericPOJOWithCreator build() {
}
}
- public void testWithBuilder() throws Exception {
+ public void testWithBuilderInferringBindings() throws Exception {
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.enable(MapperFeature.INFER_BUILDER_TYPE_BINDINGS);
+ final String json = aposToQuotes("{ 'data': [ { 'x': 'x', 'y': 'y' } ] }");
+ final MyGenericPOJO deserialized =
+ mapper.readValue(json, new TypeReference>() {});
+ assertEquals(1, deserialized.data.size());
+ Object ob = deserialized.data.get(0);
+ assertNotNull(ob);
+ assertEquals(MyPOJO.class, ob.getClass());
+ }
+
+ public void testWithBuilderWithoutInferringBindings() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final String json = aposToQuotes("{ 'data': [ { 'x': 'x', 'y': 'y' } ] }");
final MyGenericPOJO deserialized =
@@ -85,7 +100,7 @@ public void testWithBuilder() throws Exception {
assertEquals(1, deserialized.data.size());
Object ob = deserialized.data.get(0);
assertNotNull(ob);
- assertEquals(MyPOJO.class, ob.getClass());
+ assertEquals(LinkedHashMap.class, ob.getClass());
}
public void testWithCreator() throws Exception {
diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java
index f88355d900..6c0ca51f62 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java
@@ -175,13 +175,27 @@ public void testParametricTypes()
assertEquals(t, t2.containedType(1));
assertNull(t2.containedType(2));
- // and then custom generic type as well
+ // Then using TypeBindings
+ JavaType t3 = tf.constructParametricType(HashSet.class, t.getBindings()); // HashSet
+ assertEquals(CollectionType.class, t3.getClass());
+ assertEquals(1, t3.containedTypeCount());
+ assertEquals(strC, t3.containedType(0));
+ assertNull(t3.containedType(1));
+
+ // Then custom generic type as well
JavaType custom = tf.constructParametricType(SingleArgGeneric.class, String.class);
assertEquals(SimpleType.class, custom.getClass());
assertEquals(1, custom.containedTypeCount());
assertEquals(strC, custom.containedType(0));
assertNull(custom.containedType(1));
+ // and then custom generic type from TypeBindings
+ JavaType custom2 = tf.constructParametricType(SingleArgGeneric.class, t.getBindings());
+ assertEquals(SimpleType.class, custom2.getClass());
+ assertEquals(1, custom2.containedTypeCount());
+ assertEquals(strC, custom2.containedType(0));
+ assertNull(custom2.containedType(1));
+
// And finally, ensure that we can't create invalid combinations
try {
// Maps must take 2 type parameters, not just one