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