diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index 9362d3ffa3..a28913aac1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -1583,7 +1583,26 @@ protected TypeResolverBuilder _findTypeResolver(MapperConfig config, if (typeInfo.getIdType() == JsonTypeInfo.Id.NONE) { return _constructNoTypeResolverBuilder(); } - b = _constructStdTypeResolverBuilder(config, typeInfo, baseType); + + // Search for the annotation through the parent classes/hierarchies + // BEWARE: What if the annotation appears on multiple places? Is there any specific ordering? + // Could/should this be a method of Annotated? + List superTypes; + if (ann instanceof AnnotatedClass) { + superTypes = new ArrayList<>(((AnnotatedClass) ann)._superTypes); + // We want to iterate from +// Collections.reverse(superTypes); + } else { + // Could walk the class hierarchy, but how can we get a JavaType from a Class? + superTypes = Collections.emptyList(); + } + Optional optAnnotatedClass = superTypes.stream().filter(javaType -> null != javaType.getRawClass().getAnnotation(JsonTypeInfo.class)).findFirst(); + + // Fallback on the provided baseType if we can not find the annotation from the parent hierarchy of classes + // Or no fallback, as we want to keep the information later we had no clear annotationHolder + JavaType annotatedClass = optAnnotatedClass.orElse(null); + + b = _constructStdTypeResolverBuilder(config, typeInfo, baseType, annotatedClass); } // Does it define a custom type id resolver? JsonTypeIdResolver idResInfo = _findAnnotation(ann, JsonTypeIdResolver.class); @@ -1624,12 +1643,13 @@ protected StdTypeResolverBuilder _constructStdTypeResolverBuilder() { /** * Helper method for constructing standard {@link TypeResolverBuilder} * implementation. + * @param annotatedClass * * @since 2.16 (backported from Jackson 3.0) */ protected TypeResolverBuilder _constructStdTypeResolverBuilder(MapperConfig config, - JsonTypeInfo.Value typeInfo, JavaType baseType) { - return new StdTypeResolverBuilder(typeInfo); + JsonTypeInfo.Value typeInfo, JavaType baseType, JavaType annotatedClass) { + return new StdTypeResolverBuilder(typeInfo, annotatedClass); } /** 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 988f39a165..87363e9004 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 @@ -24,6 +24,11 @@ public class StdTypeResolverBuilder protected JsonTypeInfo.Id _idType; protected JsonTypeInfo.As _includeAs; + + /** + * The type representing the base class. Typically the class holding the JsonTypeInfo annotation + */ + protected JavaType _jsonTypeInfoAnnotatedClass; protected String _typeProperty; @@ -91,10 +96,11 @@ protected StdTypeResolverBuilder(StdTypeResolverBuilder base, /** * @since 2.16 */ - public StdTypeResolverBuilder(JsonTypeInfo.Value settings) { + public StdTypeResolverBuilder(JsonTypeInfo.Value settings, JavaType annotatedClass) { if (settings != null) { withSettings(settings); } + this._jsonTypeInfoAnnotatedClass = annotatedClass; } public static StdTypeResolverBuilder noTypeInfoBuilder() { @@ -353,16 +359,19 @@ protected TypeIdResolver idResolver(MapperConfig config, // Custom id resolver? if (_customIdResolver != null) { return _customIdResolver; } if (_idType == null) throw new IllegalStateException("Cannot build, 'init()' not yet called"); + + JavaType actualBaseType = _jsonTypeInfoAnnotatedClass != null ? _jsonTypeInfoAnnotatedClass : baseType; + switch (_idType) { case DEDUCTION: // Deduction produces class names to be resolved case CLASS: - return ClassNameIdResolver.construct(baseType, config, subtypeValidator); + return ClassNameIdResolver.construct(actualBaseType, config, subtypeValidator); case MINIMAL_CLASS: - return MinimalClassNameIdResolver.construct(baseType, config, subtypeValidator); + return MinimalClassNameIdResolver.construct(actualBaseType, config, subtypeValidator); case SIMPLE_NAME: - return SimpleNameIdResolver.construct(config, baseType, subtypes, forSer, forDeser); + return SimpleNameIdResolver.construct(config, actualBaseType, subtypes, forSer, forDeser); case NAME: - return TypeNameIdResolver.construct(config, baseType, subtypes, forSer, forDeser); + return TypeNameIdResolver.construct(config, actualBaseType, subtypes, forSer, forDeser); case NONE: // hmmh. should never get this far with 'none' return null; case CUSTOM: // need custom resolver... diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypesSubPackage.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypesSubPackage.java new file mode 100644 index 0000000000..ed0b8b8a3a --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypesSubPackage.java @@ -0,0 +1,47 @@ +package com.fasterxml.jackson.databind.jsontype; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.TestSubtypesSubPackage.SuperType.InnerType; +import com.fasterxml.jackson.databind.jsontype.subpackage.SubCSubPackage; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; + +public class TestSubtypesSubPackage extends DatabindTestUtil +{ + // Extended by SubCSubPackage which is in a sub package + @JsonTypeInfo(use=JsonTypeInfo.Id.MINIMAL_CLASS) + public static abstract class SuperType { + + public static class InnerType extends SuperType { + public int b = 2; + } + } + + /* + /********************************************************** + /* Unit tests + /********************************************************** + */ + + private final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + public void testSubPackage() throws Exception + { + // type should be computed consider base=SuperType (as it provides the annotation) + SubCSubPackage bean = new SubCSubPackage(); + assertEquals("{\"@c\":\".subpackage.SubCSubPackage\",\"c\":2}", MAPPER.writeValueAsString(bean)); + } + + @Test + public void testInner() throws Exception + { + // type should be computed consider base=SuperType (as it provides the annotation) + InnerType bean = new InnerType(); + assertEquals("{\"@c\":\".TestSubtypesSubPackage$SuperType$InnerType\",\"b\":2}", MAPPER.writeValueAsString(bean)); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/subpackage/SubCSubPackage.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/subpackage/SubCSubPackage.java new file mode 100644 index 0000000000..4c83900eb6 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/subpackage/SubCSubPackage.java @@ -0,0 +1,7 @@ +package com.fasterxml.jackson.databind.jsontype.subpackage; + +import com.fasterxml.jackson.databind.jsontype.TestSubtypesSubPackage; + +public class SubCSubPackage extends TestSubtypesSubPackage.SuperType { + public int c = 2; +}