Spring Boot сериализует параметризованный тип в JSON с идентификатором типа - PullRequest
0 голосов
/ 02 июля 2018

Образец проекта Spring Boot: https://github.com/yejianfengblue/spring-boot-jackson-serialize-generic-type

У меня есть вопрос, как Spring Boot сериализует общий тип в JSON с библиотекой Джексона. Пожалуйста, посмотрите мое демо:

Fruit - это общий интерфейс Apple и Banana.

BasketController имеет 3 API RESTful:

  1. getFruitList() возвращает List<Fruit>
  2. getFruitBasket() возвращает FruitBasket, который является оберткой, содержащей List<Fruit>
  3. 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:

  1. testGetFruitList(): та же строка JSON с @type, как написано Object Writer .
  2. testGetFruitBasket(): та же строка JSON с @type, как написано Object Mapper .
  3. 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();
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...