jackson-databind icon indicating copy to clipboard operation
jackson-databind copied to clipboard

AnnotationIntrospector `findPropertyTypeResolver` method generic type provides Object.class as baseType instead of actual type found in AnnotatedMember

Open aurimasniekis opened this issue 3 years ago • 6 comments

Describe the bug I have encountered the issue where my custom AnnotationIntrospector method findPropertyTypeResolver receives JavaType baseType generic Object.class for generic method, meanwhile AnnotatedMember (AnnotatedMethod::getRawReturnType()) returns correct type

Version information 2.12.6.1

To Reproduce

interface Type {}

class Foo<T of Type> {
  private T type;

  public T getType() {
    return type;
  }
}


public class Introspector extends NopAnnotationIntrospector {
  @Override
  public TypeResolverBuilder<?> findPropertyTypeResolver(
      MapperConfig<?> config, AnnotatedMember am, JavaType baseType) {

  // am is for Foo::getType() method

    var rawClass = baseType.getRawClass(); // Object.class
    if (am instanceof AnnotatedMethod amm) {
        rawClass = amm.getRawReturnType(); // Type.class
      }
    }
 }
}

Expected behavior Additional context

I know that getType() with inside _typeContext.resolveType(_method.getGenericReturnType()) see generic variable T value as Object.class and thats the reason why baseType is Object.class, but if AnnotatedMethod knows the Type.class type it would be nice if baseType would know it too...

aurimasniekis avatar Jul 21 '22 14:07 aurimasniekis

Ok, I think I'd need an actual test reproduction here to see what is happening. It is not that I don't believe there is an issue, but rather that with various call sequences there may be situations were behavior differs. I realize it may be difficult to figure out how to trigger various calls through processing (typically via construction of deserializer or serializer, for polymorphic types).

But I will also mention that the combination of generic types and polymorphic type handling is very difficult to get working, so in that sense it is not entirely surprising there might be incomplete handling somewhere.

Finally, although it is unlikely there would be actual difference, the latest released version is 2.13.3 so it'd be good to quickly verify behavior is the same, if possible.

cowtowncoder avatar Jul 21 '22 18:07 cowtowncoder

Thanks for the reply @cowtowncoder I managed to patch up some demo example, for some reason I was not able to make it run without spring in standalone, but that's not really a point so I just dropped in Spring boot and managed to replicate minimal functionality.

I also had to redo the logic after I created the ticket, I needed to fetch the type depending on what context but I think I managed to make it generic for my use case.

Here is the situation I mentioned regarding Object.class

protected TypeResolverBuilder<?> resolveTypeResolver(MapperConfig<?> config, Annotated a, JavaType baseType)
  {
    var rawClass = baseType.getRawClass();
    if (rawClass.equals(Object.class)) {
      if (a instanceof AnnotatedMethod am) {
        if (am.getParameterCount() == 1) {
          rawClass = am.getRawParameterType(0);
        } else if(am.getParameterCount() == 0) {
          rawClass = am.getRawReturnType();
        }
      } else {
        rawClass = a.getRawType();
      }
    }

https://github.com/aurimasniekis/jackson-generic-instrospector/blob/main/src/main/java/org/example/jackson_generic_instrospector/ExampleIntrospector.java#L54-L64

I am pretty new to Jackson and spend many hours in debugger until I managed to make what I wanted 😅 but I hope it's not really off the shelf :D

aurimasniekis avatar Jul 21 '22 20:07 aurimasniekis

Thank you @aurimasniekis. As you can probably guess, the problem is that whatever is calling findPropertyTypeResolver does not seem to be properly resolving the base type it passes as argument. I am glad there is a workaround and that hopefully unblocks you. But ideally of course baseType passed should be correctly resolved.

cowtowncoder avatar Jul 21 '22 20:07 cowtowncoder

No worries! I was not sure if it's a bug or expected behavior and I am just curious how Annotated interface manages to get at least an extended Type.class instead of defaulting to the typical Object.class type, but that's out of my knowledge level, I will try to dig into this more tomorrow as my OCD is kicking in too hard on hacks :D

aurimasniekis avatar Jul 21 '22 20:07 aurimasniekis

Also, I am a bit curious how Jackson decides to use setter or getter for the property type because I noticed that's it's chosen randomly 😅

aurimasniekis avatar Jul 21 '22 20:07 aurimasniekis

Ok, to, Type.class is unfortunately pretty useless: one cannot actually resolve generic types with only Type given because actual type binding is typically done somewhere else (resolved with what enclosing Class or Method has). Hence JavaType which is resolved representation, resolution of which requires full context.

Now, as to setter/getter: it is not actually chosen randomly... heuristic is simply that the precedence is based on "direction": for serialization "getter" is chosen over Field or "setter"; for deserialization reverse is true ("setter" is the primary). This ordering affects priority of annotations (which are combined across all accessors), as well as type information. But beyond having this guideline, it is possible that code in some place might select "wrong" accessor. If so, selection at very least should be stable, that is, predictable given situation. It is not the case that selection would be varying across runs, for example (or platforms) -- something that could happen for things like order of Fields or Methods JDK reports (which is not guaranteed to be predictable or stable).

cowtowncoder avatar Jul 21 '22 21:07 cowtowncoder