Spring Boot将参数化的类型序列化为ID类型为

时间:2018-07-02 03:01:17

标签: json spring-boot jackson

Spring Boot示例项目: https://github.com/yejianfengblue/spring-boot-jackson-serialize-generic-type

我对Spring Boot如何使用Jackson库将通用类型序列化为JSON存有疑问。请看我的演示:

FruitAppleBanana的通用接口。

BasketController具有3个RESTful API:

  1. getFruitList()返回一个List<Fruit>
  2. getFruitBasket()返回一个FruitBasket,它是一个包含List<Fruit>的包装器。
  3. 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字符串:

  1. testGetFruitList():与@type相同的JSON字符串,由Object Writer 编写。
  2. testGetFruitBasket():与@type相同的JSON字符串,由Object Mapper 编写。
  3. 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();
        }
    }
}

0 个答案:

没有答案