diff --git a/release-notes/VERSION b/release-notes/VERSION index d168a6c0ab..81809df153 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -12,6 +12,8 @@ Project: jackson-databind #918: Add `MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING` (contributed by David H) #933: Close some gaps to allow using the tryToResolveUnresolved flows +#936: Deserialization into List subtype with JsonCreator no longer works + (reported by adamjoeldavis@github) #948: Support leap seconds, any number of millisecond digits for ISO-8601 Dates. (contributed by Jesse W) #957: Merge `datatype-jdk7` stuff in (java.nio.file.Path handling) 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 293c6c86e4..f5a43e8c79 100644 --- a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java @@ -301,10 +301,47 @@ protected Class _findPrimitive(String className) public JavaType constructSpecializedType(JavaType baseType, Class subclass) { // simple optimization to avoid costly introspection if type-erased type does NOT differ - if (baseType.getRawClass() == subclass) { + final Class rawBase = baseType.getRawClass(); + if (rawBase == subclass) { return baseType; } + JavaType newType; + + // also: if we start from untyped, not much to save + if (rawBase == Object.class) { + newType = _fromClass(null, subclass, TypeBindings.emptyBindings()); + } else { + if (!rawBase.isAssignableFrom(subclass)) { + throw new IllegalArgumentException("Class "+subclass.getName()+" not subtype of "+baseType); + } + + // 20-Oct-2015, tatu: Container, Map-types somewhat special. There is + // a way to fully resolve and merge hierarchies; but that gets expensive + // so let's, for now, try to create close-enough approximation that + // is not 100% same, structurally, but has equivalent information for + // our specific neeeds. + if (baseType.isInterface()) { + newType = baseType.refine(subclass, TypeBindings.emptyBindings(), null, + new JavaType[] { baseType }); + } else { + newType = baseType.refine(subclass, TypeBindings.emptyBindings(), baseType, + NO_TYPES); + } + // Only SimpleType returns null, but if so just resolve regularly + if (newType == null) { + // But otherwise gets bit tricky, as we need to partially resolve the type hierarchy + // (hopefully passing null Class for root is ok) + newType = _fromClass(null, subclass, TypeBindings.emptyBindings()); + } + } + // except possibly handlers +// newType = newType.withHandlersFrom(baseType); + return newType; + + // 20-Oct-2015, tatu: Old simplistic approach + + /* // Currently mostly SimpleType instances can become something else if (baseType instanceof SimpleType) { // and only if subclass is an array, Collection or Map @@ -345,6 +382,7 @@ public JavaType constructSpecializedType(JavaType baseType, Class subclass) // otherwise regular narrowing should work just fine return baseType.narrowBy(subclass); + */ } /** @@ -725,7 +763,7 @@ public JavaType constructParametricType(Class rawType, JavaType... parameterT } /** - * @since 2.5 -- but probably deprecated in 2.7 or 2.8 (not needed with 2.7) + * @since 2.5 -- but will probably deprecated in 2.7 or 2.8 (not needed with 2.7) */ public JavaType constructParametrizedType(Class parametrized, Class parametersFor, JavaType... parameterTypes) @@ -734,7 +772,7 @@ public JavaType constructParametrizedType(Class parametrized, Class parame } /** - * @since 2.5 -- but probably deprecated in 2.7 or 2.8 (not needed with 2.7) + * @since 2.5 -- but will probably deprecated in 2.7 or 2.8 (not needed with 2.7) */ public JavaType constructParametrizedType(Class parametrized, Class parametersFor, Class... parameterClasses) @@ -1078,7 +1116,7 @@ else if (superClass != null) { context.resolveSelfReferences(result); if (key != null) { - _typeCache.put(key, result); // cache object syncs + _typeCache.putIfAbsent(key, result); // cache object syncs } return result; } @@ -1151,16 +1189,17 @@ protected JavaType _fromWellKnownInterface(ClassStack context, Class rawType, protected JavaType _fromParamType(ClassStack context, ParameterizedType ptype, TypeBindings parentBindings) { + // 20-Oct-2015, tatu: Assumption here is we'll always get Class, not one of other Types + Class rawType = (Class) ptype.getRawType(); + // First: what is the actual base type? One odd thing is that 'getRawType' // returns Type, not Class as one might expect. But let's assume it is // always of type Class: if not, need to add more code to resolve it to Class. Type[] args = ptype.getActualTypeArguments(); int paramCount = (args == null) ? 0 : args.length; JavaType[] pt; - Class rawType = (Class) ptype.getRawType(); - TypeBindings newBindings; - + if (paramCount == 0) { newBindings = EMPTY_BINDINGS; } else { diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestValueAnnotations.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestValueAnnotations.java index 4a69cc5898..f094d574a4 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestValueAnnotations.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestValueAnnotations.java @@ -276,7 +276,7 @@ public void testOverrideClassInvalid() throws Exception ("{ \"strings\" : [ ] }", BrokenCollectionHolder.class); fail("Expected a failure, but got results: "+result); } catch (JsonMappingException jme) { - verifyException(jme, "is not assignable to"); + verifyException(jme, "not subtype of"); } } diff --git a/src/test/java/com/fasterxml/jackson/failing/PolymorphicListTest936.java b/src/test/java/com/fasterxml/jackson/databind/type/PolymorphicList036Test.java similarity index 96% rename from src/test/java/com/fasterxml/jackson/failing/PolymorphicListTest936.java rename to src/test/java/com/fasterxml/jackson/databind/type/PolymorphicList036Test.java index c8e01d1bab..55506a3c3a 100644 --- a/src/test/java/com/fasterxml/jackson/failing/PolymorphicListTest936.java +++ b/src/test/java/com/fasterxml/jackson/databind/type/PolymorphicList036Test.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.failing; +package com.fasterxml.jackson.databind.type; import java.util.*; @@ -6,7 +6,7 @@ import com.fasterxml.jackson.databind.*; // For [databind#936], losing parametric type information it seems -public class PolymorphicListTest936 extends BaseMapTest +public class PolymorphicList036Test extends BaseMapTest { // note: would prefer using CharSequence, but while abstract, that's deserialized // just fine as ... String 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 898d909fcf..e5183a0664 100644 --- a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java +++ b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java @@ -276,9 +276,15 @@ public void testCollectionTypesRefined() assertEquals(Long.class, subtype.getContentType().getRawClass()); // but with refinement, should have non-null super class + // 20-Oct-2015, tatu: For now refinement does not faithfully replicate the + // structure, it only retains most important information. Here it means + // that actually existing super-classes are skipped, and only original + // type is linked as expected + /* JavaType superType = subtype.getSuperClass(); assertNotNull(superType); assertEquals(AbstractList.class, superType.getRawClass()); + */ } /* @@ -344,6 +350,12 @@ public void testMapTypesRefined() assertEquals(Integer.class, subtype.getContentType().getContentType().getRawClass()); // but with refinement, should have non-null super class + // 20-Oct-2015, tatu: For now refinement does not faithfully replicate the + // structure, it only retains most important information. Here it means + // that actually existing super-classes are skipped, and only original + // type is linked as expected + + /* JavaType superType = subtype.getSuperClass(); assertNotNull(superType); assertEquals(HashMap.class, superType.getRawClass()); @@ -351,6 +363,7 @@ public void testMapTypesRefined() assertEquals(String.class, superType.getKeyType().getRawClass()); assertEquals(List.class, superType.getContentType().getRawClass()); assertEquals(Integer.class, superType.getContentType().getContentType().getRawClass()); + */ } public void testMapTypesRaw()