是否可以 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 来自哪里。它不知道它是从 Hashtable
或 HashMap
或 TreeMap
或任何其他类型的 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中找到。
我搜索了很多,只找到关于地图内容的多态反序列化的问题。是否可以对地图本身进行多态反序列化?
比如我有一个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 来自哪里。它不知道它是从 Hashtable
或 HashMap
或 TreeMap
或任何其他类型的 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中找到。