FasterXML / FasterXML/jackson-databind

Inner classes that get their generic information from an outer class lose type information

Open
#3,288 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
3.7k
Forks
1.5k
Avg merge
3d 6h
Merged PRs (30d)
28

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**
```java
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 getValue();
}

private static final class Outer {
private final class Inner implements Wrapper {
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 implements Wrapper {
private final T value;

Direct(T value) {
this.value = value;
}

@Override
public T getValue() {
return value;
}
}

private static final class Container {
private final Wrapper wrapper;

Container(Wrapper wrapper) {
this.wrapper = wrapper;
}

public Wrapper 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().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 https://github.com/FasterXML/jackson-databind/blob/77e8b62fed4748d19fbd350c9fd80a7bdca75c91/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java#L487, but I'm not sure whether simply removing that is sufficient to resolve this.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.