Spring Boot использует REST API, используя класс с универсальным полем, которое не приведено к нужному типу. Что я делаю неправильно? - PullRequest
2 голосов
/ 23 октября 2019

Пример кода ниже.

Используя Spring Boot 2.2, я хочу связаться с REST API. API, который я пытаюсь использовать, оборачивает объекты в родительской модели для разбивки на страницы и сортировки и помещает массив фактических объектов в поле results. Как бы я смоделировал свой Java-код, чтобы Джексон «знал», как десериализовать ответы API в мои java-объекты?

Я пытался решить эту проблему с использованием универсального в ApiResponse и передатьожидаемый тип поля при выполнении запроса get:

String URL_GET_DOGS = "https://localhost/api/v1/dogs/"
ApiResponse<Dog> response = this.restTemplate.getForObject(URL_GET_DOGS, response.getClass());

Это компилирует и выполняет ...

Ожидаемый результат: Успешно создан объект ApiResponse с полем результатов, состоящим из спискаDogs.

Фактический результат: Успешно создан объект ApiResponse, но поле результатов представляет собой список объектов.

Таким образом, Джексон не будет приводить список результатов должным образом и вместо этогоПохоже, я получаю List<Object> вместо List<Dog> для моего results поля в моем объекте ApiResponse. Таким образом, я получаю свойства неправильного типа или свойства, которые вообще не хочу десериализовать! См. Пример автомобиля.

Теперь я вернулся к решению на основе интерфейса, но Я застрял. Джексон (по праву, потому что нет способа вывести правильный класс ... ) жалуется, что не знает, как десериализовать абстрактные типы, и мне нужно предоставить конкретную реализацию, я не могу использовать аннотации Джексона класса, как описано здесь , потому что я не контролирую API, генерирующийответы.

Единственный выход из этого, который я вижу сейчас, - это использование классов для каждого типа ответа, но это означает много дублирующегося кода для полей подкачки и сортировки. Что я делаю не так?

Пример JSON:

{
    "count": 84,
    "next": "http://localhost:80/api/v1/dogs/?limit=2&offset=2",
    "previous": null,
    "results": [
        {
            "name": "Pebbles"
        },
        {
            "name": "Spot"
        }
    ]
}

и другая конечная точка:

{
    "count": 22,
    "next": "http://localhost:80/api/v1/cars/?limit=2&offset=2",
    "previous": null,
    "results": [
        {
            "brand": "Mercedes",
            "horse_power": 120,
            "field_i_dont": "want_to_deserialize"
        },
        {
            "brand": "BMW",
            "horse_power": 180,
            "field_i_dont": "want_to_deserialize"
        }
    ]
}

Пример кода:

public class ApiResponse<T>{

    // paging and sorting
    private Long count;
    private String next;
    private String previous;
    // the actual objects
    private List<T> results;

    // No-args constructor, getters & setters

}

public class Dog {
     private String name;
    // No-args constructor, getters & setters

}

@JsonIgnoreProperties(ignoreUnknown = true)
public class Car {
     private int horsePower;
     private String brand;
    // No-args constructor, getters & setters

}

1 Ответ

0 голосов
/ 23 октября 2019

Причина

При дальнейшем исследовании я обнаружил, что это вызвано Type Erasure, ответ здесь на stackoverflow предоставил фрагмент для решения этой проблемы с использованием Джексона.

Отто, что я теперь понимаю, вызов response.getClass() точно такой же, как вызов ApiResponse.class. Несмотря на то, что ApiResponse был типизирован как класс с универсальным параметром, универсальные элементы «теряют» свой параметр типа из-за правила компилятора, называемого «стирание типа». Внутренний универсальный конвертируется в тип объекта, и Джексон будет использовать LinkedHashMap для представления любых данных в полях объекта.

Универсальные шаблоны используются для более строгих проверок типов во время компиляции и для обеспечения универсального программирования. Чтобы реализовать общее поведение, Java-компилятор применяет стирание типа. Стирание типа - это процесс, в котором компилятор заменяет универсальный параметр фактическим методом класса или моста. При стирании типов компилятор гарантирует, что никакие дополнительные классы не создаются и нет никаких накладных расходов времени выполнения.

Правила стирания типов

  • Замените параметры типа в универсальном типе их границей, если параметры ограниченного типа
  • Замените параметры типа в универсальном типе на Object, если используются параметры неограниченного типа.
  • Вставьте приведение типов, чтобы сохранить безопасность типов.
  • Генерация мостовых методов для сохранения полиморфизма в расширенных обобщенных типах.

Решение

Чтобы «исправить» пример в моем исходном вопросе

ApiResponse<Dog> response = this.restTemplate.getForObject(URL_GET_DOGS, response.getClass())

Необходимо изменить, чтобы можно было отображать необработанный JSON с использованием класса JavaType:

ObjectMapper mapper = new ObjectMapper(); // Defined as final in rest-client class.
String rawJsonResponse = this.restTemplate.getForObject(URL_GET_DOGS, String.class)
ApiResponse<Dog> response = mapper.readValue(
    rawJsonResponse,
    mapper.getTypeFactory().constructParametricType(
        ApiResponse.class,
        Dog.class)
    );

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...