@GetMapping方法返回带方括号JSON的原因及修复方案
问题场景
控制器代码:
@RestController public class BillsController { ... @GetMapping("get/billById/{billId}") Mono<BillDto> getBillById(@PathVariable Long billId) { log.info("Request received to fetch Bill with id : " + billId); return Mono.just(service.getBillById(billId)); } ... }
接口返回的JSON(带数组括号):
[ { "id": 3, "customer": "Larry", "products": [ { "id": 5, "name": "Bike" } ] } ]
Service方法实现:
public BillDto getBillById(Long billId) { return modelMapper.map(billRepository.findById(billId).orElseThrow(NullPointerException::new), BillDto.class); }
客户端消费时报错:
reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize value of type
library.dto.BillDtofrom Array value (tokenJsonToken.START_ARRAY); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of typelibrary.dto.BillDtofrom Array value (tokenJsonToken.START_ARRAY)
客户端代码:
Mono<BillDto> billDtoMono = client.getBilById(bill.getId()); billDtoMono.subscribe(BillDto -> { log.info(BillDto.toString()); });
注:若在@GetMapping方法内调用subscribe()并直接返回对象,响应格式正常。
原因分析
- BillDto类被Jackson识别为集合类型:如果BillDto继承了集合类(如
ArrayList)或实现了Iterable接口,Jackson会默认将其序列化为JSON数组,即使它是单个对象。 - Repository方法定义错误:若
BillRepository的findById方法返回List<Bill>而非Optional<Bill>,modelMapper在映射时可能生成异常结构,导致最终序列化结果为数组(此情况通常伴随类型转换警告)。 - Mono包装对象异常:虽然代码中用
Mono.just()包装单个对象,但如果service返回的对象实际是集合(如强制类型转换导致),也会引发数组序列化。
解决办法
1. 修正BillDto类定义
检查BillDto是否继承了集合类或实现Iterable接口,若有则移除该继承关系;若必须保留,添加Jackson注解强制序列化为对象:
import com.fasterxml.jackson.annotation.JsonFormat; @JsonFormat(shape = JsonFormat.Shape.OBJECT) public class BillDto { // 类字段、构造方法及getter/setter }
2. 验证Repository方法正确性
确保BillRepository的findById方法返回Optional<Bill>,这是Spring Data JPA的标准定义:
public interface BillRepository extends JpaRepository<Bill, Long> { Optional<Bill> findById(Long id); }
3. 调整控制器返回类型(同步场景)
若service方法是同步实现,可直接返回BillDto而非Mono<BillDto>,避免WebFlux的异步序列化逻辑干扰:
@GetMapping("get/billById/{billId}") BillDto getBillById(@PathVariable Long billId) { log.info("Request received to fetch Bill with id : " + billId); return service.getBillById(billId); }
4. 确认Mono包装的对象类型
通过调试确认service返回的是单个BillDto对象而非集合:
@GetMapping("get/billById/{billId}") Mono<BillDto> getBillById(@PathVariable Long billId) { log.info("Request received to fetch Bill with id : " + billId); BillDto dto = service.getBillById(billId); log.info("Service返回对象类型: " + dto.getClass()); // 输出应为BillDto而非集合类 return Mono.just(dto); }
客户端适配
修正接口返回格式后,现有客户端代码无需修改即可正常解析Mono<BillDto>。
内容的提问来源于stack exchange,提问作者Ramiro Serrato

