是否可以 de/serialize 在 jackson 中将自身映射为多态的?

Is it possible to de/serialize map itself polymorphic in jackson?

我搜索了很多,只找到关于地图内容的多态反序列化的问题。是否可以对地图本身进行多态反序列化?

比如我有一个Bookclass包含一个Map作为成员变量

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

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

另一个 class 有一个图书列表 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;
    }
}

还有一个测试class。一本书的评论图是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 到 review1 和 HashMap 到 review2。有什么办法可以实现吗?

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

readValue 调用不知道输入 JSON 来自哪里。它不知道它是从 HashtableHashMapTreeMap 或任何其他类型的 Map 生成的。它所要处理的只是目标类型 Shelf 及其嵌套的 Book。 Jackson 唯一可以从 Book 反省的是它有一个 Map.

类型的字段

Map是一个接口。由于无法实例化接口,因此 Jackson 必须决定要使用的 Map 的实现类型。默认情况下,它使用 LinkedHashMap。您可以按照 here.

发布的解决方案更改默认值

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

private HashMap<String, Object> reviews;

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

实际的解决方案是让你不用关心实际的实现class。您决定将其设为 Map。您不应该关心它在幕后使用什么实现。使用多态的力量。

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

我在 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中找到。