Spring Boot示例项目: https://github.com/yejianfengblue/spring-boot-jackson-serialize-generic-type
我对Spring Boot如何使用Jackson库将通用类型序列化为JSON存有疑问。请看我的演示:
Fruit
是Apple
和Banana
的通用接口。
BasketController
具有3个RESTful API:
getFruitList()
返回一个List<Fruit>
getFruitBasket()
返回一个FruitBasket
,它是一个包含List<Fruit>
的包装器。getBasketOfFruit()
返回一个Basket<Fruit>
,它是一个包含List<T>
的包装器。在BasketController
中,我记录了从Jackson Object Mapper 和Object Writer 写入的JSON字符串。请在日志语句下面的注释中查看实际输出。
BasketController.java
/** <li>Object<b>Mapper</b>.writeValueAsString, @type is lost due to java type erasure.
* <li>Object<b>Writer</b>.writeValueAsString, @type is kept */
@GetMapping(path = "getFruitList")
public List<Fruit> getFruitList() throws JsonProcessingException {
List<Fruit> fruitList = Arrays.asList(new Apple(1), new Banana(2));
log.info("List<Fruit> mapper.writeValueAsString: {}", mapper.writeValueAsString(fruitList));
// log: List<Fruit> mapper.writeValueAsString: [{"wgt":1},{"wgt":2}]
ObjectWriter writer = mapper.writerFor(new TypeReference<List<Fruit>>() {});
log.info("List<Fruit> writer.writeValueAsString: {}", writer.writeValueAsString(fruitList));
// log: List<Fruit> writer.writeValueAsString: [{"@type":"Apple","wgt":1},{"@type":"Banana","wgt":2}]
return fruitList;
}
/** Object<b>Mapper</b>.writeValueAsString, with a wrapper of {@code List<Fruit>}, @type is kept */
@GetMapping(path = "getFruitBasket")
public FruitBasket getFruitBasket() throws JsonProcessingException {
FruitBasket fruitBasket = new FruitBasket();
fruitBasket.getItems().add(new Apple(3));
fruitBasket.getItems().add(new Banana(4));
log.info("FruitBasket mapper.writeValueAsString: {}", mapper.writeValueAsString(fruitBasket));
// log: FruitBasket mapper.writeValueAsString: {"items":[{"@type":"Apple","wgt":3},{"@type":"Banana","wgt":4}]}
return fruitBasket;
}
/** <li>Object<b>Mapper</b>.writeValueAsString, @type is lost due to java type erasure.
* <li>Object<b>Writer</b>.writeValueAsString, @type is kept */
@GetMapping(path = "getBasketOfFruit")
public Basket<Fruit> getBasketOfFruit() throws JsonProcessingException {
Basket<Fruit> basketOfFruit = new Basket<Fruit>();
basketOfFruit.getItems().add(new Apple(5));
basketOfFruit.getItems().add(new Banana(6));
log.info("Basket<Fruit> mapper.writeValueAsString: {}", mapper.writeValueAsString(basketOfFruit));
// log: Basket<Fruit> mapper.writeValueAsString: {"items":[{"wgt":5},{"wgt":6}]}
ObjectWriter writer = mapper.writerFor(new TypeReference<Basket<Fruit>>() {});
log.info("Basket<Fruit> writer.writeValueAsString: {}", writer.writeValueAsString(basketOfFruit));
// log: Basket<Fruit> writer.writeValueAsString: {"items":[{"@type":"Apple","wgt":5},{"@type":"Banana","wgt":6}]}
return basketOfFruit;
}
我知道Java类型会删除List<Fruit>
在运行时被视为List<?>
。
SpringBootBlueApplicationTests
按顺序测试3个RESTful API,并打印由Spring Boot序列化的JSON字符串:
testGetFruitList()
:与@type相同的JSON字符串,由Object Writer 编写。testGetFruitBasket()
:与@type相同的JSON字符串,由Object Mapper 编写。testGetBasketOfFruit()
:与Object Mapper 编写的相同的JSON字符串没有 @type。SpringBootBlueApplicationTests.java
@Autowired
private TestRestTemplate restTemplate;
/** Same json string as Object<b>Writer</b> writes with @type in BasketController.getFruitList() */
@Test
public void testGetFruitList() {
log.info("/getFruitList JSON: {}", restTemplate.getForObject("/getFruitList", String.class));
// log: /getFruitList JSON: [{"@type":"Apple","wgt":1},{"@type":"Banana","wgt":2}]
}
/** Same json string as Object<b>Mapper</b> writes with @type in BasketController.getFruitBasket() */
@Test
public void testGetFruitBasket() {
log.info("/getFruitBasket JSON: {}", restTemplate.getForObject("/getFruitBasket", String.class));
// log: /getFruitBasket JSON: {"items":[{"@type":"Apple","wgt":3},{"@type":"Banana","wgt":4}]}
}
/** Same json string as Object<b>Mapper</b> writes without @type in BasketController.getBasketOfFruit().
* What I want is the string as Object<b>Writer</b> writes with @type */
@Test
public void testGetBasketOfFruit() {
log.info("/getBasketOfFruit JSON: {}", restTemplate.getForObject("/getBasketOfFruit", String.class));
// log: /getBasketOfFruit JSON: {"items":[{"wgt":5},{"wgt":6}]}
}
在RESTful API 1 getFruitList()
中进行JSON序列化期间,Spring Boot知道Fruit
中的实际运行时类型List<Fruit>
。
问题:
如何使Spring Boot在RESTful API 3 Basket<Fruit>
中序列化getBasketOfFruit
,使其包含@type?
我在https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-json-components中找到有关自定义JSON序列化器和反序列化器的参考。
但是我不知道如何编写覆盖方法serialize()
。
初步解决方案
分支prelim_solution_with_jsonserializer https://github.com/yejianfengblue/spring-boot-jackson-serialize-generic-type/tree/prelim_solution_with_jsonserializer
我注册了2个JsonSerializer,现在Basket<Fruit>
可以通过@type进行序列化,但是我不知道这是否是最好的方法。
BasketJsonSerializer.java
@JsonComponent
public class BasketJsonSerializer {
public static class Serializer extends JsonSerializer<Basket<Fruit>> {
@Override
public void serialize(Basket<Fruit> value, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeObjectField("items", value.getItems());
gen.writeEndObject();
}
}
}
FruitListJsonSerializer.java
@JsonComponent
public class FruitListJsonSerializer {
public static class Serializer extends JsonSerializer<List<Fruit>> {
@Override
public void serialize(List<Fruit> list, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
gen.writeStartArray();
for (Fruit fruit : list)
gen.writeObject(fruit);
gen.writeEndArray();
}
}
}