Spring Boot RestTemplate映射JSON的空值与动态键反序列化问题
HasOffers findAll接口反序列化异常解决方案
核心问题1:大量字段映射为null的排查&修复
按优先级逐个排查:
- 层级不匹配:HasOffers接口返回结构存在固定多层嵌套,
api_key在最外层request节点下,status/httpStatus/data都在response节点下,如果直接把这些字段平铺定义在最外层响应类,除了刚好位置对的几个字段,其余全为null。 - 命名策略不匹配:接口返回字段默认是下划线命名(如
offer_id、default_payout),Spring Boot内置Jackson默认用驼峰命名匹配,要么给字段加@JsonProperty("原字段名")指定映射关系,要么全局配置Jackson命名策略为下划线转驼峰。 - 缺少Setter方法:Jackson反序列化默认依赖public修饰的setter方法给字段赋值,如果实体类字段是私有属性、没写setter也没加Lombok
@Data注解自动生成方法,字段无法被赋值。
核心问题2:动态key的data节点映射方案
HasOffers类接口的data节点不是标准JSON数组,是以资源ID为动态key、单条资源为value的对象结构,不要定义为List<Offer>接收,直接用JDK自带的Map<String, 单条资源包装类>接收即可,Jackson会自动完成动态键值对的反序列化。
注意:data下每个单条offer的内容还会包裹在固定key为Offer的子对象中,需要单独定义包装层承接。
可直接复用的代码实现
1. 响应实体类定义
import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.math.BigDecimal; import java.util.List; import java.util.Map; @Data public class HasOffersApiResponse { private RequestInfo request; private ResponseBody response; @Data public static class RequestInfo { @JsonProperty("api_key") private String apiKey; } @Data public static class ResponseBody { private Integer status; private Integer httpStatus; private List<Object> errors; // 动态key节点直接用Map接收,key为字符串类型的offerID private Map<String, OfferEntry> data; } @Data public static class OfferEntry { // 承接固定key为Offer的子节点 @JsonProperty("Offer") private Offer offer; } @Data public static class Offer { private String id; private String name; private BigDecimal defaultPayout; private String previewUrl; // 其余字段按照接口文档的返回key逐个补充即可 } }
2. 全局Jackson配置(可选,省掉逐个写字段映射注解)
在application.yml中添加配置,自动完成下划线命名字段到驼峰实体字段的映射:
spring: jackson: property-naming-strategy: SNAKE_CASE
3. RestTemplate调用逻辑
import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.List; import java.util.stream.Collectors; @Service public class OfferService { @Resource private RestTemplate restTemplate; public List<Offer> fetchAllOffers() { String reqUrl = "https://你的HasOffers接口地址/Offer/findAll?NetworkId=你的ID&api_key=你的密钥"; ResponseEntity<HasOffersApiResponse> resp = restTemplate.getForEntity(reqUrl, HasOffersApiResponse.class); HasOffersApiResponse body = resp.getBody(); // 从动态Map中提取所有offer实体,转成普通List使用 return body.getResponse().getData().values() .stream() .map(HasOffersApiResponse.OfferEntry::getOffer) .collect(Collectors.toList()); } }
验证注意点
- 先拿接口原始返回的JSON片段,对照实体类层级逐层核对,不要凭感觉写实体结构。
- 如果个别字段还是为null,直接打印原始响应字符串,确认接口本身是否返回了对应字段,不要默认所有字段接口都会返回。
内容的提问来源于stack exchange,提问作者Gabriel Vendramini
相关产品推荐
相关产品推荐

