question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Non-generic interface method hides type resolution info from generic base class

See original GitHub issue

I saw this issue via MongoJack and haven’t yet been able to construct a pure jackson databind test case but it seems that in some circumstances a class definition like the following:

public interface I {
   String getT();
}

public class A<T> {
    private T t;
    public A(T t) { this.t = t; }
    public T getT() { return t; }
}

public class B extends A<String> implements I {
    public B(String t) { super(t); }
}

can result in the type information required to resolve the type of t to a String is lost.

This happens because in AnnotatedClass._addMemberMethods is this code:

                /* 06-Jan-2010, tatu: [JACKSON-450] Except that if method we saw first is
                 *   from an interface, and we now find a non-interface definition, we should
                 *   use this method, but with combination of annotations.
                 *   This helps (or rather, is essential) with JAXB annotations and
                 *   may also result in faster method calls (interface calls are slightly
                 *   costlier than regular method calls)
                 */
                if (old.getDeclaringClass().isInterface() && !m.getDeclaringClass().isInterface()) {
                    methods.add(old.withMethod(m));
                }

In my test, the interface getter is discovered first and so plays the role of “old”, then the base class getter is being added and so the original definintion of the method is simply adjusted with a reference to the method being processed. However the method from the interface has no type resolution information since the interface is not defined with generics so the type resolution information is lost and thus t can end up being serialialised as Object instead of String and so a serialisation failure occurs.

I implemented a fix as follows. To AnnotatedMethod add the method:

    public AnnotatedMethod withContextAndMethod(TypeResolutionContext ctxt, Method m) {
        return new AnnotatedMethod(ctxt, m, _annotations, _paramAnnotations);
    }

and then change the code in AnnotatedClass to:

                if (old.getDeclaringClass().isInterface() && !m.getDeclaringClass().isInterface()) {
                    methods.add(old.withContextAndMethod(typeContext, m));
                }

which resolved my problem. Happy to submit a pull request, but I haven’t got the CLA ready yet and I would also like some help constructing a pure Jackson unit test to replicate the issue.

To be specific about my MongoJack reproducer:

public interface I {
   String getT();
}

public class A<T> {
    private T t;
    public A(T t) { this.t = t; }
    public T getT() { return t; }
}

public class B extends A<String> implements I {
    public B(String t) { super(t); }
}

@Test
public void test() {
		ObjectMapper om = new ObjectMapper();
		MongoJackModule.configure(om);
		DBQuery.Query query = DBQuery.is("_id", "t");

		JavaType bType = om.getTypeFactory().constructType(B.class);
		DBObject serialized = SerializationUtils.serializeQuery(om, bType, query);
		System.out.println(serialized);
}

Prints { "_id": "t" } as expected with my fix and throws this exception without it:

org.mongojack.MongoJsonMappingException: Error serializing value t in DBQuery operation _id
	at org.mongojack.internal.util.SerializationUtils.serializeQueryField(SerializationUtils.java:266)
	at org.mongojack.internal.util.SerializationUtils.serializeQueryCondition(SerializationUtils.java:182)
	at org.mongojack.internal.util.SerializationUtils.serializeQuery(SerializationUtils.java:155)
	at org.mongojack.internal.util.SerializationUtils.serializeQuery(SerializationUtils.java:143)
	at bugs.jackson.selfpolymorph.test(selfpolymorph.java:69)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.lang.String and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
	at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:284)
	at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1110)
	at com.fasterxml.jackson.databind.SerializerProvider.reportMappingProblem(SerializerProvider.java:1135)
	at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:69)
	at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:32)
	at org.mongojack.internal.util.SerializationUtils.serializeQueryField(SerializationUtils.java:264)
	... 27 more

My testing has been using the 2.8 branch of jackson databind.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
sjzcommented, Sep 28, 2017

Thanks for this @cowtowncoder, @tbartley

Came across this issue today. Not sure if it was intentional - the line that fixes the issue, as I am experiencing it, was commented out. Comment here: https://github.com/FasterXML/jackson-databind/commit/48fc70bfd3938440782b66ced08c3420ac2dbfa1#commitcomment-24618605

0reactions
cowtowncodercommented, Sep 29, 2017

Actually, since this was in 2.9.1, I can re-open, re-fix it without too much confusion…

Read more comments on GitHub >

github_iconTop Results From Across the Web

C# & generics - why is method in base class called instead of ...
If a generic class with constraint T:Base tries to call method Foo on an object of type T , what should the return...
Read more >
Non-generic Wrapper instead of Base Class or Interface
A common solution to treating a generic type as non-generic is to implement an interface or make the generic type extend from a...
Read more >
Chapter 11: Generics - CODE Magazine
The fundamental problem with creating classes that can work with multiple data types without generics is that they must use a common base...
Read more >
Generic Methods - C# Programming Guide | Microsoft Learn
Learn about methods declared with type parameters, known as generic methods. See code examples and view additional available resources.
Read more >
Generics in C# - Performance is a Feature!
C# permits classes, structs, interfaces and methods to be parameterized by the types of data they store and manipulate, through a set of...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found