Repository navigation
Inner classes that get their generic information from an outer class lose type information #3288
Copy link
Copy link
Open
Description
Describe the bug
If a non-static inner class uses generics from the outer class, that generic information is lost when serializing.
Version information
2.12.5
To Reproduce
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
public final class JacksonPoC {
@JsonTypeInfo(use = Id.NAME)
@JsonSubTypes({@JsonSubTypes.Type(value = ConcreteValue.class, name = "concrete-value")})
private interface AbstractValue {}
private static class ConcreteValue implements AbstractValue {}
public interface Wrapper<T> {
T getValue();
}
private static final class Outer<T> {
private final class Inner implements Wrapper<T> {
private final T value;
Inner(T value) {
this.value = value;
}
@Override
public T getValue() {
return value;
}
}
Inner create(T value) {
return new Inner(value);
}
}
private static final class Direct<T> implements Wrapper<T> {
private final T value;
Direct(T value) {
this.value = value;
}
@Override
public T getValue() {
return value;
}
}
private static final class Container {
private final Wrapper<AbstractValue> wrapper;
Container(Wrapper<AbstractValue> wrapper) {
this.wrapper = wrapper;
}
public Wrapper<AbstractValue> getWrapper() {
return wrapper;
}
}
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void direct() throws Exception {
// This test passes because the generic information is present on the class directly
Container object = new Container(new Direct<>(new ConcreteValue()));
assertThat(mapper.writeValueAsString(object))
.isEqualTo("{\"wrapper\":{\"value\":{\"@type\":\"concrete-value\"}}}");
}
@Test
void indirect() throws Exception {
// This test fails (the @type part is omitted) because the `AbstractValue` information is lost
Container object = new Container(new Outer<AbstractValue>().create(new ConcreteValue()));
assertThat(mapper.writeValueAsString(object))
.isEqualTo("{\"wrapper\":{\"value\":{\"@type\":\"concrete-value\"}}}");
}
}Additional context
The example above does feel quite contrived, but I encountered this in practice with Guava's Multimaps.asMap on a SetMultimap, because the resulting class AbstractMapBasedMultimap.WrappedSet inherits the generic information from the parent.
When debugging this, I got as far as the short-circuit in
jackson-databind/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java
Line 487 in 77e8b62
| if (typeParamCount == 0) { |
Metadata
Metadata
Assignees
Labels
No labels
Activity