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