是否有可能在杰克逊中对地图本身进行de / serialize多态?

时间:2015-09-26 13:08:27

标签: java json dictionary jackson polymorphism

我搜索了很多内容,只发现了有关地图内容的多态反序列化的问题。是否有可能对地图本身进行多态反序列化?

例如,我有一个Book类包含一个Map作为成员变量。

public class Book {
    @JsonProperty
    private Map<String, Object> reviews;

    @JsonCreator
    public Book(Map<String, Object> map) {
        this.reviews = map;
    }
}

另一个班级有一个Book类列表。

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;
    }
}

还有一个测试课。一本书的评论地图是Hashtable,另一本书的评论地图是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());
        }
    }
}

测试输出

{
  "name" : "TestShelf",
  "books" : [ {
    "reviews" : {
      "test1" : "review1"
    }
  }, {
    "reviews" : {
      "test2" : "review2"
    }
  } ]
}
class java.util.LinkedHashMap
class java.util.LinkedHashMap

序列化工作正常。但是在反序列化之后,review1和review2都是LinkedHashMap。我希望review1和review2成为他们的实际类型,它们是Hashtable to review1和HashMap to review2。有没有办法实现这个目标?

我不想使用mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);因为它会在json消息中为所有json属性添加类型信息。如果有更好的方法,我也不想使用定制的解串器。提前谢谢。

2 个答案:

答案 0 :(得分:1)

我在Jackson用户论坛上发布了这个问题,他们建议自定义TypeResolverBuilder并在ObjectMapper实例中设置它。

ObjectMapper.setDefaultTyping(...)

我的自定义TypeResolverBuilder位于下方,它解决了我的问题。

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();
    }
}

此解决方案要求服务器端和客户端都使用自定义TypeResolverBuilder。我知道它并不理想,但它是我迄今为止找到的最佳解决方案。有关解决方案的详细信息,请参阅我的博客中的post

答案 1 :(得分:0)

readValue调用不知道输入JSON来自何处。它不知道它是从HashtableHashMapTreeMap或任何其他类型的Map生成的。它必须使用的是目标类型Shelf及其嵌套Book。杰克逊唯一可以从Book反省的是它有一个Map类型的字段。

Map是一个界面。由于您无法实例化接口,因此Jackson必须对其要使用的Map的实现类型做出决定。默认情况下,它使用LinkedHashMap。您可以按照发布的here解决方案更改默认值。

另一种方法是使用您想要的具体类型声明字段

private HashMap<String, Object> reviews;

现在杰克逊知道将JSON反序列化为HashMap。显然,这只适用于单一类型。

实际的解决方案是您不关心实际的实现类。你决定它将成为Map。你不应该关心它在幕后使用的实现。使用多态的力量。

(请注意,长期以来不鼓励使用Hashtable。)