jackson-databind: Deserialization Not Working Right with Generic Types and Builders
When trying to deserialize a generic type using a builder it is deserializing the generic type as a LinkedHashMap instead of the proper type, exact same code deserialized using @JsonCreator
works fine. There seems to be something missing in the builder code.
Test case below demonstrates the problem:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.junit.Test;
import java.util.List;
public class JacksonDeserTest {
public static class MyPOJO {
public String x;
public String y;
@JsonCreator
public MyPOJO(@JsonProperty("x") String x, @JsonProperty("y") String y) {
this.x = x;
this.y = y;
}
}
@JsonDeserialize(builder = MyGenericPOJO.Builder.class)
public static class MyGenericPOJO<T> {
private List<T> data;
private MyGenericPOJO(List<T> data) {
this.data = data;
}
public List<T> getData() {
return data;
}
public static class Builder<T> {
private List<T> data;
public Builder<T> withData(List<T> data) {
this.data = data;
return this;
}
public MyGenericPOJO<T> build() {
return new MyGenericPOJO<T>(data);
}
}
}
public static class MyGenericPOJOWithCreator<T> {
private List<T> data;
private MyGenericPOJOWithCreator(List<T> data) {
this.data = data;
}
@JsonCreator
public static <T> MyGenericPOJOWithCreator<T> create(@JsonProperty("data") List<T> data) {
return new MyGenericPOJOWithCreator.Builder<T>().withData(data).build();
}
public List<T> getData() {
return data;
}
public static class Builder<T> {
private List<T> data;
public Builder<T> withData(List<T> data) {
this.data = data;
return this;
}
public MyGenericPOJOWithCreator<T> build() {
return new MyGenericPOJOWithCreator<T>(data);
}
}
}
@Test
public void testWithBuilder() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final String json = "{ \"data\": [ { \"x\": \"x\", \"y\": \"y\" } ] }";
final MyGenericPOJO<MyPOJO> deserialized =
mapper.readValue(json, new TypeReference<MyGenericPOJO<MyPOJO>>() {});
assertThat(deserialized.data, hasSize(1));
assertThat(deserialized.data.get(0), instanceOf(MyPOJO.class));
}
@Test
public void testWithCreator() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final String json = "{ \"data\": [ { \"x\": \"x\", \"y\": \"y\" } ] }";
final MyGenericPOJOWithCreator<MyPOJO> deserialized =
mapper.readValue(json, new TypeReference<MyGenericPOJOWithCreator<MyPOJO>>() {});
assertThat(deserialized.data, hasSize(1));
assertThat(deserialized.data.get(0), instanceOf(MyPOJO.class));
}
}
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Reactions: 4
- Comments: 28 (17 by maintainers)
Commits related to this issue
- Add a unit test for #921 — committed to FasterXML/jackson-databind by cowtowncoder 9 years ago
- Use JsonCreator instead of builder due to the unresolved bug: https://github.com/FasterXML/jackson-databind/issues/921 — committed to neoteric-eu/neo-starters by poznachowski 8 years ago
- Integration tests for builder deserialization with Jackson. The generic test fails due to a bug in Jackson - https://github.com/FasterXML/jackson-databind/issues/921 - and is currently disabled. — committed to ArpNetworking/commons by deleted user 8 years ago
- Integration tests for builder deserialization with Jackson. The generic test fails due to a bug in Jackson - https://github.com/FasterXML/jackson-databind/issues/921 - and is currently disabled. (#16) — committed to ArpNetworking/commons by vjkoskela 8 years ago
- Fix #921 (backported from `master`) — committed to FasterXML/jackson-databind by vjkoskela 6 years ago
- Comment out one failing test wrt #921: was assuming wrong things about static methods, type variables — committed to FasterXML/jackson-databind by cowtowncoder 4 years ago
As per
milestone
indicated on the right side, was fixed for version 2.12.0 so yes, 2.12.x and 2.13.x should have the fix.It’s been a while since I’ve had a chance to write some Java. Well, I got back into it, and wasn’t long before I was using Jackson. That of course reminded that I may owe you @cowtowncoder a few things.
It looks like this was 2.9 and then 2.11. Is there anything on this issue I can do to move it forward and get it included in an upcoming release?
I ran into this issue with Spring’s
RestTemplate
. I use Lombok and the@JsonPOJOBuilder
- annotation to trick Jackson into deserializing my immutable object, but once a type parameter enters the play things get hairy. I would not mind using the snapshot of Jackson that includes the newMapperFeature.INFER_BUILDER_TYPE_BINDINGS
, but Spring does - so I’m stuck here. What’s the latest state on a workaround that doesn’t involve an additional creator method?Thanks @spharris !
I took the example in the tests and expanded it a little to see how it behaved (modified code at the end). I have not nailed down all the semantics of the generic type mapping but here are some preliminary results.
Works:
Does not Work:
The failure seems to occur regardless of whether the pojo itself was parameterized (e.g. MyPOJO<Z>) or if MyPOJO were not parameterized at all.
The conclusion I am drawing so far is that
@JsonCreator
deserialization already does generic type inference between the creator method parameters and the provided type reference. Therefore, it seems less unreasonable to have the Builder pattern do the same thing.I have not found where in the data bind code this path infers the generic parameters from the type reference. Any help either here or with understanding my conclusion would be appreciated (@cowtowncoder ?) .
If my conclusion is correct, then in my opinion it follows that the MR https://github.com/FasterXML/jackson-databind/pull/1796 probably should be the default behavior – but I’m open either way – as it seems to bring builder based deserialization behavior more inline with creator based deserialization.
Full modified test (
com.fasterxml.jackson.failing.BuilderDeserializationTest921
):^^ Executed against tag 2.9
Why not make the dumb assumption, check the build method returns the correct type given that assumption, and error out if anything looks wrong?