使用@Jacksonized @Builder @Value注解序列化POJO产生空JSON问题排查
Lombok+Jackson序列化Payload为空问题解决方案
版本信息
- lombok: 1.18.20
- spring-boot: 2.5.4
lombok.config配置(IntelliJ默认生成)
# add @lombok.Generated annotation to all Lombok generated methods config.stopBubbling = true lombok.addLombokGeneratedAnnotation = true
相关代码
Payload.java
@Jacksonized @Builder @Value public class Payload<T> { @Getter(AccessLevel.NONE) private T content; /** * @return payload content */ public T get() { return content; } }
ApiRequest.java
@Jacksonized @Builder @Value public class ApiRequest<T> { private Date timestamp; private Payload<T> payload; }
Metadata.java
@Jacksonized @Builder(toBuilder = true) @Value public class Metadata { private String element; private String threshold; private String groupType; private String groupId; private String groupName; private String groupPath; private Instant from; private Instant to; }
MyRestController.java
@Slf4j @Validated @RestController @RequestMapping(value = "/test", produces = MediaType.APPLICATION_JSON_VALUE) @RequestScope public class MyRestController { @RequestMapping( path = "/process", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ApiResponse process(@RequestBody ApiRequest<List<Metadata>> apiRequest) { log.info("Received request: " + apiRequest); return null; // 暂时忽略返回 } /*public ApiResponse process(@RequestBody String json) { log.info("Received request: " + json); return null; // 暂时忽略返回 }*/ }
TestClass.java
public class TestClass { public static void main(String[] args) throws Exception { String fromStr = "2021-09-05T00:00:00Z"; String toStr = "2021-09-11T23:59:59Z"; Instant from = Instant.parse(fromStr); Instant to = Instant.parse(toStr); String groupName = "SomeGroupName"; String groupPath = "SomeGroupPath"; List<Metadata> metadataList = new ArrayList<>(); metadataList.add(Metadata.builder().element("e1").threshold("48").groupName(groupName).groupPath(groupPath).from(from).to(to).build()); metadataList.add(Metadata.builder().element("e2").threshold("35").groupName(groupName).groupPath(groupPath).from(from).to(to).build()); metadataList.add(Metadata.builder().element("e3").threshold("90").groupName(groupName).groupPath(groupPath).from(from).to(to).build()); Payload<List<Metadata>> payload = Payload.<List<Metadata>>builder().content(metadataList).build(); ApiRequest<List<Metadata>> apiRequest = ApiRequest.<List<Metadata>>builder().payload(payload).build(); RestTemplate rt = new RestTemplate(); final String baseUrl = "http://localhost:8080/test/process"; URI uri = new URI(baseUrl); ResponseEntity<ApiResponse> response = rt.postForEntity(uri, apiRequest, ApiResponse.class); ApiResponse r = response.getBody(); } }
问题现象
运行代码时,MyRestController收到的apiRequest为null,即使取消控制器中注释的接收原始JSON字符串的方法,也能看到payload字段为空,实际打印的请求内容如下:
Received request: {"timestamp":null,"payload":{}}
疑问
- 本次序列化失败的错误点在哪里?
- 序列化正常后,ApiResponse反序列化是否需要特殊处理?
要求优先通过@Jacksonized注解实现功能。
已验证解决方案
问题出在Payload类的定义上,修改代码即可解决:
@Jacksonized @Builder @Value public class Payload<T> { @Getter(AccessLevel.NONE) private T content; /** * @return payload content */ @JsonProperty("content") // 新增该注解指定Jackson使用此getter获取content字段 public T get() { return content; } }
原因说明
通过@Getter(AccessLevel.NONE)关闭了Lombok为content字段自动生成的标准JavaBean getter方法getContent()后,自定义的get()方法不符合Jackson默认的getter命名规则,Jackson无法将该方法和content字段关联,导致序列化时读取不到content的值,最终payload序列化结果为空。添加@JsonProperty("content")后会显式告知Jackson该方法对应content字段的读方法,就能正常完成序列化和反序列化流程。
ApiResponse处理说明
只要ApiResponse类也使用@Jacksonized + @Builder + @Value的组合注解,且内部没有类似Payload这种自定义的不符合命名规范的getter/setter,就不需要额外做特殊处理。@Jacksonized注解已经自动完成了Jackson和Lombok Builder的适配,Spring Boot 2.5.4默认的Jackson配置完全兼容该注解,直接使用即可。
内容的提问来源于stack exchange,提问作者curious_brain
相关产品推荐
相关产品推荐

