diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index e98efb7c5a..17d72dc396 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -4,6 +4,11 @@ Project: jackson-databind === Releases === ------------------------------------------------------------------------ +2.9.6 (not yet released) + +#1565: Deserialization failure with Polymorphism using JsonTypeInfo `defaultImpl`, + subtype as target + 2.9.5 (26-Mar-2018) #1911: Allow serialization of `BigDecimal` as String, using diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index 3f8f9481e0..9fd4201e26 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonCreator.Mode; import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig; import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; @@ -16,6 +17,7 @@ import com.fasterxml.jackson.databind.deser.impl.CreatorCollector; import com.fasterxml.jackson.databind.deser.impl.JavaUtilCollectionsDeserializers; import com.fasterxml.jackson.databind.deser.std.*; +import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; import com.fasterxml.jackson.databind.ext.OptionalHandlerFactory; import com.fasterxml.jackson.databind.introspect.*; import com.fasterxml.jackson.databind.jsontype.NamedType; @@ -1554,9 +1556,8 @@ public TypeDeserializer findTypeDeserializer(DeserializationConfig config, AnnotationIntrospector ai = config.getAnnotationIntrospector(); TypeResolverBuilder b = ai.findTypeResolver(config, ac, baseType); - /* Ok: if there is no explicit type info handler, we may want to - * use a default. If so, config object knows what to use. - */ + // Ok: if there is no explicit type info handler, we may want to + // use a default. If so, config object knows what to use. Collection subtypes = null; if (b == null) { b = config.getDefaultTyper(baseType); @@ -1574,7 +1575,16 @@ public TypeDeserializer findTypeDeserializer(DeserializationConfig config, b = b.defaultImpl(defaultType.getRawClass()); } } - return b.buildTypeDeserializer(config, baseType, subtypes); + // 05-Apt-2018, tatu: Since we get non-mapping exception due to various limitations, + // map to better type here + try { + return b.buildTypeDeserializer(config, baseType, subtypes); + } catch (IllegalArgumentException e0) { + InvalidDefinitionException e = InvalidDefinitionException.from((JsonParser) null, + e0.getMessage(), baseType); + e.initCause(e0); + throw e; + } } /** diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerFactory.java index ffba4e443c..d063852091 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerFactory.java @@ -184,7 +184,7 @@ public abstract JsonDeserializer createMapLikeDeserializer(DeserializationCon public abstract KeyDeserializer createKeyDeserializer(DeserializationContext ctxt, JavaType type) throws JsonMappingException; - + /** * Method called to find and create a type information deserializer for given base type, * if one is needed. If not needed (no polymorphic handling configured for type), diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java index 04c8f1f06c..17d5ec72fe 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.annotation.NoClass; import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.jsontype.*; -import com.fasterxml.jackson.databind.util.ClassUtil; /** * Default {@link TypeResolverBuilder} implementation. @@ -143,13 +142,17 @@ public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, defaultImpl = config.getTypeFactory() .constructSpecializedType(baseType, _defaultImpl); } else { - // 05-Apr-2018, tatu: [databind#1861] Not sure what would be the best way - // to handle, but for 2.9, let's consider case of "sibling" defaultImpl... - // ... Ugh. Not declared to throw `JsonMappingException`, so... + // 05-Apr-2018, tatu: As [databind#1565] and [databind#1861] need to allow + // some cases of seemingly incompatible `defaultImpl`. Easiest to just clear + // the setting. + + /* throw new IllegalArgumentException( String.format("Invalid \"defaultImpl\" (%s): not a subtype of basetype (%s)", ClassUtil.nameOf(_defaultImpl), ClassUtil.nameOf(baseType.getRawClass())) ); + */ + defaultImpl = null; } } } diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl1565.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl1565.java new file mode 100644 index 0000000000..26e7286da4 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl1565.java @@ -0,0 +1,94 @@ +package com.fasterxml.jackson.databind.jsontype; + +import com.fasterxml.jackson.annotation.*; + +import com.fasterxml.jackson.databind.*; + +public class TestPolymorphicWithDefaultImpl1565 extends BaseMapTest +{ + // [databind#1565] + @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, + property="typeInfo", defaultImpl = CBaseClass1565.class) + @JsonSubTypes({ + @JsonSubTypes.Type(CDerived1565.class) + }) + public static interface CTestInterface1565 + { + public String getName(); + public void setName(String name); + public String getTypeInfo(); + } + + static class CBaseClass1565 implements CTestInterface1565 + { + private String mName; + + @Override + public String getName() { + return(mName); + } + + @Override + public void setName(String name) { + mName = name; + } + + @Override + public String getTypeInfo() { + return "base"; + } + } + + @JsonTypeName("derived") + static class CDerived1565 extends CBaseClass1565 + { + public String description; + + @Override + public String getTypeInfo() { + return "derived"; + } + } + + // [databind#1861] + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = DefaultImpl1861.class) + @JsonSubTypes({ + @JsonSubTypes.Type(name = "a", value = Impl1861A.class) + }) + static abstract class Bean1861 { + public String base; + } + + static class DefaultImpl1861 extends Bean1861 { + public int id; + } + + static class Impl1861A extends Bean1861 { + public int valueA; + } + + /* + /********************************************************************** + /* Test methods + /********************************************************************** + */ + + private final ObjectMapper MAPPER = new ObjectMapper(); + + // [databind#1565] + public void testIncompatibleDefaultImpl1565() throws Exception + { + String value = "{\"typeInfo\": \"derived\", \"name\": \"John\", \"description\": \"Owner\"}"; + CDerived1565 result = MAPPER.readValue(value, CDerived1565.class); + assertNotNull(result); + } + + // [databind#1861] + public void testWithIncompatibleTargetType1861() throws Exception + { + // Should allow deserialization even if `defaultImpl` incompatible + Impl1861A result = MAPPER.readValue(aposToQuotes("{'type':'a','base':'foo','valueA':3}"), + Impl1861A.class); + assertNotNull(result); + } +} diff --git a/src/test/java/com/fasterxml/jackson/failing/SubTypeResolution1964Test.java b/src/test/java/com/fasterxml/jackson/failing/SubTypeResolution1964Test.java new file mode 100644 index 0000000000..f6d9bc9572 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/failing/SubTypeResolution1964Test.java @@ -0,0 +1,43 @@ +package com.fasterxml.jackson.failing; + +import java.util.*; + +import com.fasterxml.jackson.databind.*; + +@SuppressWarnings("serial") +public class SubTypeResolution1964Test extends BaseMapTest +{ + static class AccessModel { + private Map> repositoryPrivileges; + + public AccessModel() { + repositoryPrivileges = new HashMap<>(); + } + + public Map> getRepositoryPrivileges() { + return repositoryPrivileges; + } + + public void setRepositoryPrivileges(Map> repositoryPrivileges) { + this.repositoryPrivileges = repositoryPrivileges; + } + } + + static class CustomMap extends LinkedHashMap { } + + public void testTypeCompatibility1964() throws Exception + { + Map> repoPrivilegesMap = new CustomMap<>(); + String key = "/storages/storage0/releases"; + Collection values = new HashSet<>(); + values.add("ARTIFACTS_RESOLVE"); + repoPrivilegesMap.put(key, values); + + AccessModel accessModel = new AccessModel(); + accessModel.setRepositoryPrivileges(repoPrivilegesMap); + + ObjectMapper mapper = new ObjectMapper(); + String jsonStr = mapper.writeValueAsString(accessModel); + assertNotNull(jsonStr); + } +}