Образец проекта Spring Boot:
https://github.com/yejianfengblue/spring-boot-jackson-serialize-generic-type
У меня есть вопрос, как Spring Boot сериализует общий тип в JSON с библиотекой Джексона. Пожалуйста, посмотрите мое демо:
Fruit
- это общий интерфейс Apple
и Banana
.
BasketController
имеет 3 API RESTful:
getFruitList()
возвращает List<Fruit>
getFruitBasket()
возвращает FruitBasket
, который является оберткой, содержащей List<Fruit>
getBasketOfFruit()
возвращает Basket<Fruit>
, который является оберткой, содержащей List<T>
В BasketController
я записываю строку JSON, написанную из объектов Джексона Mapper и Object Writer . Пожалуйста, посмотрите фактический результат в комментарии под оператором журнала.
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 API RESTful по порядку и печатает строку JSON, сериализованную Spring Boot:
testGetFruitList()
: та же строка JSON с @type, как написано Object Writer .
testGetFruitBasket()
: та же строка JSON с @type, как написано Object Mapper .
testGetBasketOfFruit()
: та же строка JSON без @type, как написано Object Mapper .
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}]}
}
Во время сериализации JSON в RESTful API 1 getFruitList()
Spring Boot знает фактический тип времени выполнения Fruit
из List<Fruit>
.
Вопрос:
Как я могу заставить сериализовать Spring Boot Basket<Fruit>
в RESTful API 3 getBasketOfFruit
, чтобы он содержал @type?
Я нахожу ссылку на пользовательские сериализаторы и десериализаторы JSON в https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-json-components.
Но я понятия не имею, как написать метод переопределения 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();
}
}
}