I've searched a lot and only find questions about polymorphic deserialization on the content inside a map. Is it possible to polymorphic deserializing the map itself?
For example, I have a Book class contains a Map as a member variable.
public class Book {
@JsonProperty
private Map<String, Object> reviews;
@JsonCreator
public Book(Map<String, Object> map) {
this.reviews = map;
}
}
Another class have a list of Book class.
public class Shelf {
@JsonProperty
private List<Book> books = new LinkedList<>();
public void setBooks(List<Book> books) {
this.books = books;
}
public List<Book> getBooks() {
return this.books;
}
}
And a test class. One book's review map is a Hashtable and another book's review map is a HashMap.
public class Test {
private Shelf shelf;
@BeforeClass
public void init() {
Map<String, Object> review1 = new Hashtable<>(); // Hashtable here
review1.put("test1", "review1");
Map<String, Object> review2 = new HashMap<>(); // HashMap here
review2.put("test2", "review2");
List<Book> books = new LinkedList<>();
books.add(new Book(review1));
books.add(new Book(review2));
shelf = new Shelf();
shelf.setBooks(books);
}
@Test
public void test() throws IOException{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
String json = mapper.writeValueAsString(shelf);
System.out.println(json);
Shelf sh = mapper.readValue(json, Shelf.class);
for (Book b : sh.getBooks()) {
System.out.println(b.getReviews().getClass());
}
}
}
The test output
{
"name" : "TestShelf",
"books" : [ {
"reviews" : {
"test1" : "review1"
}
}, {
"reviews" : {
"test2" : "review2"
}
} ]
}
class java.util.LinkedHashMap
class java.util.LinkedHashMap
The serialization works fine. But after deserialization, both review1 and review2 are LinkedHashMap. I want review1 and review2 to be their actual types which are Hashtable to review1 and HashMap to review2. Is there any way to achieve this?
I don't want to use mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
because it will add the type info for all json properties in the json message. And if there is any better way to do it I don't want to use customized deserializer either. Thanks in advance.
I posted the question on Jackson user forum and they suggest to customized the TypeResolverBuilder
and set it in the ObjectMapper instance.
ObjectMapper.setDefaultTyping(...)
My customized TypeResolverBuilder
is below and it solved my problem.
public class MapTypeIdResolverBuilder extends StdTypeResolverBuilder {
public MapTypeIdResolverBuilder() {
}
@Override
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config,
JavaType baseType, Collection<NamedType> subtypes) {
return useForType(baseType) ? super.buildTypeDeserializer(config, baseType, subtypes) : null;
}
@Override
public TypeSerializer buildTypeSerializer(SerializationConfig config,
JavaType baseType, Collection<namedtype> subtypes) {
return useForType(baseType) ? super.buildTypeSerializer(config, baseType, subtypes) : null;
}
/**
* Method called to check if the default type handler should be
* used for given type.
* Note: "natural types" (String, Boolean, Integer, Double) will never
* use typing; that is both due to them being concrete and final,
* and since actual serializers and deserializers will also ignore any
* attempts to enforce typing.
*/
public boolean useForType(JavaType t) {
return t.isMapLikeType() || t.isJavaLangObject();
}
}
This solution requires both server side and client side to use the customized TypeResolverBuilder
. I know it is not ideal, but it is the best solution I found so far. The details of the solution can be found in this post on my blog.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With