Spring Boot serialize parameteried type into JSON with type id


Spring Boot serialize parameteried type into JSON with type id



Spring Boot sample project:
https://github.com/yejianfengblue/spring-boot-jackson-serialize-generic-type



I have question in how Spring Boot serializes generic type into JSON with Jackson library. Please see my demo:



Fruit is a common interface of Apple and Banana.


Fruit


Apple


Banana



BasketController has 3 RESTful API:


BasketController


getFruitList()


List<Fruit>


getFruitBasket()


FruitBasket


List<Fruit>


getBasketOfFruit()


Basket<Fruit>


List<T>



In BasketController, I log the JSON string written from Jackson ObjectMapper and ObjectWriter. Please see the actual output in the comment below the log statement.


BasketController



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



I know Java type erasure that List<Fruit> is treated as List<?> in run-time.


List<Fruit>


List<?>



SpringBootBlueApplicationTests tests the 3 RESTful API in order, and prints the JSON string serialized by Spring Boot:


SpringBootBlueApplicationTests


testGetFruitList()


testGetFruitBasket()


testGetBasketOfFruit()



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



During JSON serialization in RESTful API 1 getFruitList(), Spring Boot knows the actual runtime type Fruit of List<Fruit>.


getFruitList()


Fruit


List<Fruit>



Question:



How can I make Spring Boot serializes Basket<Fruit> in RESTful API 3 getBasketOfFruit, so that it contains @type?


Basket<Fruit>


getBasketOfFruit



I find a reference about Custom JSON Serializers and Deserializers in https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-json-components.
But I have no clue in how to write the override method serialize().


serialize()



Preliminary Solution



Branch prelim_solution_with_jsonserializer https://github.com/yejianfengblue/spring-boot-jackson-serialize-generic-type/tree/prelim_solution_with_jsonserializer



I register 2 JsonSerializers and now Basket<Fruit> can be serialized with @type, but I don't know whether it's the best way to do it.


Basket<Fruit>



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









By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

api-platform.com Unable to generate an IRI for the item of type

How to set up datasource with Spring for HikariCP?

Display dokan vendor name on Woocommerce single product pages