Перейти к основному содержимому

Получить список объектов JSON с Spring RestTemplate

· 4 мин. чтения

1. Обзор

Нашим службам часто приходится взаимодействовать с другими службами REST для получения информации.

В Spring мы можем использовать RestTemplate для выполнения синхронных HTTP-запросов. Данные обычно возвращаются в формате JSON, и RestTemplate может преобразовать их для нас.

В этом руководстве мы рассмотрим, как мы можем преобразовать массив JSON в три разные структуры объектов в Java : массив объектов , массив POJO и список POJO . ``

2. JSON, POJO и сервис

Давайте представим, что у нас есть конечная точка,

http://localhost:8080/users

возвращающая список пользователей в виде следующего JSON:

[{
"id": 1,
"name": "user1",
}, {
"id": 2,
"name": "user2"
}]

Нам потребуется соответствующий класс User для обработки данных:

public class User {
private int id;
private String name;

// getters and setters..
}

Для реализации нашего интерфейса мы пишем UserConsumerServiceImpl с RestTemplate в качестве зависимости:

public class UserConsumerServiceImpl implements UserConsumerService {

private final RestTemplate restTemplate;

public UserConsumerServiceImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

...
}

3. Сопоставление списка объектов JSON

Когда ответ на запрос REST представляет собой массив JSON, мы можем преобразовать его в коллекцию Java несколькими способами.

Давайте посмотрим на параметры и посмотрим, насколько легко они позволяют нам обрабатывать возвращаемые данные. Мы рассмотрим извлечение имен пользователей некоторых пользовательских объектов, возвращаемых службой REST.

3.1. RestTemplate с массивом объектов

Во-первых, давайте сделаем вызов с помощью RestTemplate.getForEntity и используем ResponseEntity типа Object[] для получения ответа:

ResponseEntity<Object[]> responseEntity =
restTemplate.getForEntity(BASE_URL, Object[].class);

Далее мы можем извлечь тело в наш массив Object :

Object[] objects = responseEntity.getBody();

Фактический объект здесь — это просто некоторая произвольная структура, которая содержит наши данные, но не использует наш тип пользователя . Давайте конвертируем его в наши объекты User .

Для этого нам понадобится ObjectMapper :

ObjectMapper mapper = new ObjectMapper();

Мы можем объявить его встроенным, хотя обычно это делается как закрытый статический конечный член класса.

Наконец, мы готовы извлечь имена пользователей:

return Arrays.stream(objects)
.map(object -> mapper.convertValue(object, User.class))
.map(User::getName)
.collect(Collectors.toList());

С помощью этого метода мы можем по существу прочитать массив чего угодно в массив объектов в Java. Это может быть удобно, если мы хотим, например, только подсчитать результаты.

Однако он плохо поддается дальнейшей обработке. Нам пришлось приложить дополнительные усилия, чтобы преобразовать его в тип, с которым мы могли работать.

Десериализатор Джексона фактически десериализует JSON в серию объектов LinkedHashMap , когда мы просим его создать Object в качестве целевого типа. Постобработка с convertValue — это неэффективные накладные расходы.

Мы можем избежать этого, если предоставим желаемый тип Джексону в первую очередь.

3.2. RestTemplate с пользовательским массивом

Мы можем предоставить User[] в RestTemplate вместо Object[] :

ResponseEntity<User[]> responseEntity = 
restTemplate.getForEntity(BASE_URL, User[].class);
User[] userArray = responseEntity.getBody();
return Arrays.stream(userArray)
.map(User::getName)
.collect(Collectors.toList());

Мы видим, что нам больше не нужен ObjectMapper.convertValue . Внутри ResponseEntity есть объекты User . Но нам все еще нужно выполнить некоторые дополнительные преобразования, чтобы использовать Java Stream API и чтобы наш код работал со списком.

3.3. RestTemplate со списком пользователей и ParameterizedTypeReference

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

Этот метод принимает ParameterizedTypeReference , созданный анонимным внутренним классом :

ResponseEntity<List<User>> responseEntity = 
restTemplate.exchange(
BASE_URL,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<User>>() {}
);
List<User> users = responseEntity.getBody();
return users.stream()
.map(User::getName)
.collect(Collectors.toList());

Это создает список , который мы хотим использовать.

Давайте подробнее рассмотрим, зачем нам нужно использовать ParameterizedTypeReference .

В первых двух примерах Spring может легко десериализовать JSON в токен типа User.class , где информация о типе полностью доступна во время выполнения.

Однако с дженериками стирание типа происходит, если мы пытаемся использовать List<User>.class . Таким образом, Джексон не сможет определить тип внутри <> .

Мы можем преодолеть это, используя токен супертипа под названием ParameterizedTypeReference . Создание его как анонимного внутреннего класса — new ParameterizedTypeReference<List<User>>() {} — использует тот факт, что подклассы универсальных классов содержат информацию о типах во время компиляции, которая не подлежит стиранию типа и может быть использована посредством отражения.

4. Вывод

В этой статье мы увидели три разных способа обработки объектов JSON с помощью RestTemplate . Мы увидели, как указывать типы массивов Object и наши собственные пользовательские классы.

Затем мы узнали, как мы предоставляем информацию о типе для создания списка с помощью ParameterizedTypeReference .

Как всегда, код для этой статьи доступен на GitHub .