Open jmax01 opened 8 years ago
Interesting. I can reproduce this as per above.
I think the problem comes from difference between handling of @JsonTypeInfo
, and default typing:
@JsonTypeInfo
has special handling for structured types, as there's no way to indicate how it should apply to contents (in retrospect, it probably should have simple contents=
property to allow separate definition), and so Jackson does same as JAXB -- it only applies to elements/values of arrays, Collection
s and Map
s (and nowadays, referential types like Optional
); but for non-structured types, value itselfCollection
and its elementsSo what I think happens here is that @JsonTypeInfo
only affects handling of elements in Collection
, but not Collection
, thereby leaving default typing in effect.
Now... there are couple of ways in which this could be handled, perhaps. For example:
Id.NONE
a special case that prevents use of type info for both structured value and its elements -- that seems a likely intent@JsonTypeInfo
to allow specifying that it should apply to value itself, not elements, even for structured types. This could either be a boolean, or enumeration -- enum allowing more choices (value, elements, both).Of these, (2) would be safer, and potentially more usable, although it still would not allow defining type info settings for both container and values separately. But it could only be added in next minor version (2.9). (1) on the other hand could be added in a patch version; most likely 2.8. It would however be slightly higher risk; theoretically it could break existing code I guess I am leaning slightly on (2), as it would be cleaner.
Now... since this is not as simple a fix as I first hoped, a work-around you may want is to specify custom handler for deciding where/when to apply default typing:
public ObjectMapper setDefaultTyping(TypeResolverBuilder<?> typer) { ... }
so, you need to call that instead of enableDefaultTyping()
.
Class `DefaultTypeResolverBuilder
(within ObjectMapper
) shows how to implement logic.
Deserialization actually throws an exception:
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.SortedSet;
import java.util.TreeSet;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.google.common.collect.ImmutableSortedSet;
@SuppressWarnings("javadoc")
public class ObjectAndNonConcreteCollectionsTest {
final static ObjectMapper WITH_OBJECT_AND_NON_CONCRETE = new ObjectMapper().enable(
SerializationFeature.INDENT_OUTPUT)
.enableDefaultTypingAsProperty(DefaultTyping.OBJECT_AND_NON_CONCRETE, "$type")
.registerModule(new GuavaModule());
final static ObjectMapper PLAIN_OBJECT_MAPPER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT)
.registerModule(new GuavaModule());
static class MyClass {
private SortedSet<String> treeSetStrings;
private SortedSet<String> immutableSortedSetStrings;
@JsonTypeInfo(use = Id.NONE)
public SortedSet<String> getTreeSetStrings() {
return this.treeSetStrings;
}
@JsonTypeInfo(use = Id.NONE)
public SortedSet<String> getImmutableSortedSetStrings() {
return this.immutableSortedSetStrings;
}
public void setImmutableSortedSetStrings(ImmutableSortedSet<String> immutableSortedSetStrings) {
this.immutableSortedSetStrings = immutableSortedSetStrings;
}
public void setTreeSetStrings(TreeSet<String> treeSetStrings) {
this.treeSetStrings = treeSetStrings;
}
}
@Test
public void testCollectionTypingSerialization() throws IOException {
final MyClass toSerialize = new MyClass();
toSerialize.setImmutableSortedSetStrings(ImmutableSortedSet.of());
toSerialize.setTreeSetStrings(new TreeSet<>());
final String fromPlainObjectMapper = PLAIN_OBJECT_MAPPER.writeValueAsString(toSerialize);
System.out.println("\nfrom plain ObjectMapper:\n" + fromPlainObjectMapper);
final String fromObjectMapperWithDefaultTyping = WITH_OBJECT_AND_NON_CONCRETE.writeValueAsString(toSerialize);
System.out.println("\nfrom ObjectMapper with default typing:\n" + fromObjectMapperWithDefaultTyping);
assertEquals(fromPlainObjectMapper, fromObjectMapperWithDefaultTyping);
}
@Test
public void testCollectionTypingDeserialization() throws IOException {
final MyClass toSerialize = new MyClass();
toSerialize.setImmutableSortedSetStrings(ImmutableSortedSet.of());
toSerialize.setTreeSetStrings(new TreeSet<>());
final String fromPlainObjectMapper = PLAIN_OBJECT_MAPPER.writeValueAsString(toSerialize);
System.out.println("\nfrom plain ObjectMapper:\n" + fromPlainObjectMapper);
final String fromObjectMapperWithDefaultTyping = WITH_OBJECT_AND_NON_CONCRETE.writeValueAsString(toSerialize);
System.out.println("\nfrom ObjectMapper with default typing:\n" + fromObjectMapperWithDefaultTyping);
//Works
final MyClass roundTripPlainObjectMapper = PLAIN_OBJECT_MAPPER.readValue(fromPlainObjectMapper, MyClass.class);
// Fails with:
// com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of
// START_ARRAY token
// at [Source: {
// "treeSetStrings" : [ "java.util.TreeSet", [ ] ],
// "immutableSortedSetStrings" : [ "com.google.common.collect.EmptyImmutableSortedSet", [ ] ]
// }; line: 2, column: 45] (through reference chain: MyClass["treeSetStrings"]->java.util.TreeSet[1])
final MyClass roundObjectMapperWithDefaultTyping = WITH_OBJECT_AND_NON_CONCRETE.readValue(
fromObjectMapperWithDefaultTyping, MyClass.class);
}
}
Version: Jackson 2.8.2 JDK: 1.8.0_60
It appears that enableDefaultTypingAsProperty adds type info to collections regardless if @JsonTypeInfo(use = Id.NONE) is specified on the property.