Spring RestTemplate 解析 JSON 数组

1、概览

在 Spring Boot 应用中,一般使用 RestTemplate 来执行同步 HTTP 请求。数据通常以 JSON 的形式返回,而 RestTemplate 可以进行自动转换。

本文将带你了解,如何在 RestTemplate 中把响应的 JSON 数组转换为 Object 数组、POJO 数组和 POJO 集合。

2. JSON、POJO 和 Service

假设我们有一个端点 http://localhost:8080/users,它返回如下 JSON 格式的用户列表:

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

对应的 User 类如下:

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

    // get/set 方法省略
}

定义 Service 接口实现 UserConsumerServiceImpl,并注入 RestTemplate

public class UserConsumerServiceImpl implements UserConsumerService {

    private final RestTemplate restTemplate;

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

...
}

3、映射 JSON 数组

当 REST 请求的响应是一个 JSON 数组时,有几种方法可以将其转换为 Java 集合(Collection)。

3.1、Object 数组

首先,调用 RestTemplate.getForEntity 方法,并使用 Object[] 类型的 ResponseEntity 来收集响应:

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

接下来,可以将 Body 提取到 Object 数组中:

Object[] objects = responseEntity.getBody();

这里的 Object 实际上只是一些包含数据的任意结构(例如:Map),但并没有使用上述的 User 类,需要把它转换成 User 对象。

为此,需要一个 ObjectMapper

ObjectMapper mapper = new ObjectMapper();

最后,把 Object 转换为 User 对象,并收集其 name 属性:

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

使用这种方法,可以将任意类型的数组读取为 Java 中的 Object 数组。这在只想计算结果数量时非常方便。

然而,它并不适合进一步处理。因为这需要花费更多精力将其转换成可以使用的具体类型。

当设置 Jackson Deserializer 使用 Object 作为目标类型时,它实际上会将 JSON 反序列化为一系列 LinkedHashMap 对象。使用 convertValue 再次进行转换这会增加额外的开销。

如果一开始就向 Jackson 提供想要的类型,就可以避免这种情况。

3.2、User 数组

可以向 RestTemplate 提供具体的 User[],而不是 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、通过 ParameterizedTypeReference 指定 List<User> 类型

如果需要 Jackson 直接生成 List<User> 而不是数组,可以调用 RestTemplate.exchange 方法,并通过 ParameterizedTypeReference 指定泛型结果。

该方法接收由匿名内部类生成的 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());

这种方式,就可以直接获取到所需要的 List<User>

为什么需要使用 ParameterizedTypeReference

在前两个示例中,Spring 可以轻松地将 JSON 反序列化为 User.class 类型,其中的类型信息在运行时完全可用。

然而,在使用泛型时,如果尝试使用 List<User>.class,就会发生类型擦除。Jackson 无法确定 <> 内的类型。

可以通过使用 ParameterizedTypeReference 类来解决这一问题。将其实例化为匿名内部类 - new ParameterizedTypeReference<List<User>>() {} - 利用泛型类的子类包含编译时类型信息这一特性,这些信息不会被类型擦除,并且可以通过反射来使用。

4、总结

本文介绍了如何使用 RestTemplate 把响应的 JSON 数组反序列化为 Java 的 Object 数组、自定义的 POJO 数组以及泛型的 POJO 集合(使用 ParameterizedTypeReference)。


Ref:https://www.baeldung.com/spring-resttemplate-json-list