本文详解如何在 Spring WebFlux 中将多个异步请求结果聚合为单个嵌套 JSON 对象(如 { "shipment": { "111": [...], "222": [...] } }),避免 Flux 默认产生的数组结构,通过 Mono + collectMap + DTO 实现语义清晰、格式精准的响应。
本文详解如何在 spring webflux 中将多个异步请求结果聚合为单个嵌套 json 对象(如 `{ "shipment": { "111": [...], "222": [...] } }`,避免 flux 默认产生的数组结构,通过 mono + collectmap + dto 实现语义清晰、格式精准的响应。
在 Spring WebFlux 中,使用 Flux 返回多个元素时,Jackson 默认将其序列化为 JSON 数组(如 [{"111":[...]}, {"222":[...]}]),但这与前端期望的单个对象内嵌命名字段(如 "shipment": {...})结构严重不符。根本原因在于:Flux
✅ 正确实现路径:Mono + collectMap + 分层 DTO
推荐采用类型安全、可维护性强的 DTO 方式,而非嵌套泛型(如 Mono
1. 定义响应 DTO(含语义化结构)
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ShipmentResponse {
@JsonUnwrapped // 确保序列化时不包裹在 "shipment" 键下(见下方说明)
private Shipment shipment;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Shipment {
private Map<string list>> shipment; // 字段名与外层 key 一致,便于映射
}
}</string>
⚠️ 注意:@JsonUnwrapped 的作用是取消 Shipment 类自身的包装键。若不加此注解,会得到 { "shipment": { "shipment": { ... } } } 的双重嵌套。此处利用 Lombok + Jackson 组合,使 ShipmentResponse.shipment.shipment 最终扁平化为 "shipment": { ... }。
2. 重构服务方法:从 Flux 聚合到 Mono
import org.springframework.data.util.Pair;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public Mono<shipmentresponse> getShipment(List<string> shipmentOrderNumbers) {
final String apiUrl = "/some-path";
final String queryParam = "some-query-param";
ParameterizedTypeReference<list>> expectedType =
new ParameterizedTypeReference<list>>() {};
return Flux.fromIterable(shipmentOrderNumbers)
.flatMap(orderNo -> this.client.get()
.uri(uriBuilder -> uriBuilder.path(apiUrl)
.queryParam(queryParam, orderNo).build())
.retrieve()
.bodyToMono(expectedType)
.map(items -> Pair.of(orderNo, items))) // 将每个响应转为 (orderNo, itemList) 对
.collectMap(Pair::getFirst, Pair::getSecond) // 聚合成 Map<string list>>
.map(Shipment::new) // 封装进内部 Shipment DTO
.map(ShipmentResponse::new); // 构建顶层响应对象
}</string></list></list></string></shipmentresponse>
3. 控制器层(示例)
@GetMapping("/shipments")
public Mono<shipmentresponse> fetchShipments(@RequestParam List<string> orderNos) {
return shipmentService.getShipment(orderNos);
}</string></shipmentresponse>
? 关键要点总结
- 勿用 Flux:它必然生成 JSON 数组,违背单对象响应契约;
-
优先 collectMap():将流式键值对高效聚合为 Map
>,是构建嵌套结构的核心步骤; - DTO 分层设计:ShipmentResponse(顶层容器) + Shipment(逻辑分组)提升可读性与可扩展性;
- @JsonUnwrapped 是关键:它确保 Shipment 实例的字段直接作为父对象属性输出,而非嵌套子对象;
-
依赖项提醒:Pair 来自 spring-data-commons(Spring Boot 项目通常已引入),若未存在,请添加:
<dependency><groupid>org.springframework.data</groupid><artifactid>spring-data-commons</artifactid></dependency>
通过以上方式,你将稳定输出符合预期的 JSON:
{
"shipment": {
"111": ["ITEM1", "ITEM2", "ITEM3"],
"222": ["ITEM4", "ITEM5", "ITEM6"]
}
}
既满足 REST API 的语义规范,又保持响应结构的清晰性与可维护性。










