Problem deserializing Java 8 `Optional` with Polymorphic Deserialization with Jackson-databind 2.12 vs 2.8
See original GitHub issueDescribe the bug We recently tried to upgrade our application’s Jackson dependencies from 2.8.x to 2.12.x. After the upgrade though, we noticed some incompatible behaviors regarding the serialization and de-serialization results.
Code Setup
We have a POJO called RequestParameter, and it contains a metadata field:
private final Optional<Map<String, Object>> metadata;
This field is initialized in the constructor like the following:
this.metadata = Optional.of(ImmutableMap.copyOf(metadata.get()));
(The ImmutableMap here is the Guava ImmutableMap).
We use Mixins with the following configurations to specify the default type resolving behavior for the Map interface: (This might not be the best way to specify the default resolving behaviors for polymorphic fields, but our code was already setup this way…)
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE, defaultImpl = HashMap.class)
public static class MapMixin {}
Note that we are using JsonTypeInfo.Id.NONE
here to drop the type information for Map.
How we create our ObjectMapper:
ObjectMapper mapper = new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.addMixInAnnotations(ServerMessage.class, FrameworkMessageMixin.class);
mapper.addMixInAnnotations(ClientMessage.class, FrameworkMessageMixin.class);
mapper.registerSubtypes(...);
// some more configurations
objectMapper.enableDefaultTypingAsProperty(DefaultTyping.NON_CONCRETE_AND_ARRAYS,
JsonCoderSpecification.TYPE_PROPERTY);
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);
objectMapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE);
objectMapper.registerModule(new Jdk8Module());
objectMapper.addMixIn(Map.class, MapMixin.class);
objectMapper.addMixIn(List.class, ListMixin.class);
objectMapper.addMixIn(Set.class, SetMixin.class);
Observed Behaviors
Let’s say we have a RequestParameter POJO, and it’s metadata field has one entry in it: "testKey":"testValue"
. Below are the serialization and deserialization results for this POJO under different Jackson versions:
With 2.8.x
// Serialized result
{"__type__":"RequestParameter", "metadata":["java.util.Optional",{"testKey":"testValue"}]}
// Deserialized result for the metadata field (based on POJO toString)
metadata=Optional[{testKey=testValue}]
With 2.12.x (and even 2.10.x)
// Serialized result
{"__type__":"RequestParameter", "metadata":{"__type__":"com.google.common.collect.SingletonImmutableBiMap","testKey":"testValue"}}
// Deserialized result for the metadata field (based on POJO toString)
metadata=Optional[{__type__=com.google.common.collect.SingletonImmutableBiMap, testKey=testValue}]
As we can see, with 2.12.x, the JsonTypeInfo.Id.NONE
specified in the Mixin is not really working, as the class/type info is still somehow added during the serialization process. The Optional wrapper for the metadata field is also somehow missing in the serialized result. As a result, when we try to deserialize the String back to the RequestParameter POJO, we end up having a Map with 2 entries, which is inaccurate.
Please help look into this incompatible behavior. Thank you!
Version information Old: Jackson-databind = 2.8.x; Jackson-datatype-jdk8 = 2.8.x;
New: Jackson-databind = 2.12.x; Jackson-datatype-jdk8 = 2.12.x;
**Issues that are related
Issue Analytics
- State:
- Created 2 years ago
- Comments:13 (7 by maintainers)
Top GitHub Comments
I’ll make this quick: Polymorphic Type Information for referential types like
Optional
should always be for content value, and never for “Optional” itself. This is the behavior expected with 2.12 and later, regardless of what 2.8 did. Unlike with other Container types (Collections, Maps), there is no marker (Array, Object) on which Type Id could be attached, so the choice is between contents (almost always more important) or nominal Optional wrapper (rarely more important one).So, the expectation is that Type Id for content value, if any, should be output (assuming Polymorphic Typing is enabled for (nominal) value type – if it’s not, no Type Id should be written or expected).
Put another way, Type Id for
Optional
should quite literally never be written by serialization.Updated POJO to remove Lombok dependencies:
The test results I posted earlier can be reproduced with both jackson 2.8 and 2.12.
As a side note, based on the current test POJO and test suite, if I change the
AtomicReference
toOptional
, and add back theJdk8Module
dependency, then the original inconsistent results can be reproduced.With 2.8.x, the test would pass:
With 2.12.x, the test would fail: