Jackson无法识别父类字段:子类反序列化报错问题
问题场景
使用Lombok的@Builder和Jackson的@Jacksonized注解实现父类Parent与子类Child的序列化/反序列化时,执行测试用例抛出UnrecognizedPropertyException,提示父类的recipient字段在Child$ChildRequestBuilder中不被识别。
Parent父类代码
@Builder @Jacksonized @EqualsAndHashCode @ToString @Getter @Setter @JsonInclude(JsonInclude.Include.NON_NULL) @AllArgsConstructor @NoArgsConstructor @JsonTypeInfo(use = DEDUCTION) @JsonSubTypes({ @JsonSubTypes.Type(value = Child.class, name = "Child") }) public class Parent implements Serializable { private static final long serialVersionUID = 6223930820946596247L; @JsonProperty("recipient") protected Recipient recipient; // 其他字段和方法 }
Child子类代码
@Builder(builderMethodName = "childRequestBuilder") @Jacksonized @EqualsAndHashCode @ToString @Getter @Setter @AllArgsConstructor @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) public class Child extends Parent implements Serializable { private static final long serialVersionUID = -2848064640409441165L; @JsonProperty("use_case") private String useCase; }
异常信息
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "recipient" (class a.b.Child$ChildRequestBuilder),
not marked as ignorable (1 known properties: "use_case"]) at [Source:
(String)"{"recipient":{"endpoint_details":{"someToken":"someToken","someDevice":"someDevice","someApp":"someApp"}}}";;
line: 1, column: 129] (through reference chain:
a.b.Child$ChildRequestBuilder["recipient"])
测试代码
@Test public void testChildDeserialization() throws JsonProcessingException { SomeEndPoint endPoint = SomeEndpoint.builder() .someToken("someToken") .someDevice("someDevice") .someApp("someApp") .build(); Recipient recipient = Recipient.builder().endpointDetails(endPoint).build(); Child child = Child.childRequestBuilder().build(); child.setRecipient(recipient); String request = new ObjectMapper().writeValueAsString(child); Child deserialisedChild = new ObjectMapper().readValue(request, Child.class); SomeEndPoint someEndPoint = (SomeEndPoint) deserialisedChild.getRecipient().getEndpointDetails(); Assert.assertEquals(someEndPoint.getSomeToken(), endPoint.getSomeToken()); }
解决方案
核心原因
普通的@Builder注解不会自动将父类字段继承到子类的Builder中,而@Jacksonized在反序列化时会尝试通过Builder构建对象,导致父类字段无法被Builder识别,从而抛出异常。
具体修复步骤
替换普通@Builder为@SuperBuilder
父类和子类都需要使用Lombok的@SuperBuilder注解(要求Lombok版本≥1.18.16),该注解专门处理继承场景下的Builder模式,会自动将父类字段继承到子类Builder中。- 修改Parent类的
@Builder为@SuperBuilder - 修改Child类的
@Builder为@SuperBuilder
修改后的Parent类代码:
@SuperBuilder @Jacksonized @EqualsAndHashCode @ToString @Getter @Setter @JsonInclude(JsonInclude.Include.NON_NULL) @AllArgsConstructor @NoArgsConstructor @JsonTypeInfo(use = DEDUCTION) @JsonSubTypes({ @JsonSubTypes.Type(value = Child.class, name = "Child") }) public class Parent implements Serializable { // 字段和其他内容不变 }修改后的Child类代码:
@SuperBuilder(builderMethodName = "childRequestBuilder") @Jacksonized @EqualsAndHashCode @ToString @Getter @Setter @AllArgsConstructor @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) public class Child extends Parent implements Serializable { // 字段和其他内容不变 }- 修改Parent类的
优化测试代码(可选但推荐)
修复后可直接通过Builder设置父类字段,无需手动调用setter方法,更符合Builder模式设计:@Test public void testChildDeserialization() throws JsonProcessingException { SomeEndPoint endPoint = SomeEndpoint.builder() .someToken("someToken") .someDevice("someDevice") .someApp("someApp") .build(); Recipient recipient = Recipient.builder().endpointDetails(endPoint).build(); // 直接通过Builder设置父类的recipient字段 Child child = Child.childRequestBuilder() .recipient(recipient) .build(); String request = new ObjectMapper().writeValueAsString(child); Child deserialisedChild = new ObjectMapper().readValue(request, Child.class); SomeEndPoint someEndPoint = (SomeEndPoint) deserialisedChild.getRecipient().getEndpointDetails(); Assert.assertEquals(someEndPoint.getSomeToken(), endPoint.getSomeToken()); }备选方案(不推荐)
若无法升级Lombok版本使用@SuperBuilder,可手动在Child的Builder中添加父类字段的设置方法,但需手动维护,容易遗漏或出错:public class Child extends Parent implements Serializable { // 字段不变 public static class ChildRequestBuilder extends Parent.ParentBuilder<Child, ChildRequestBuilder> { private String useCase; public ChildRequestBuilder useCase(String useCase) { this.useCase = useCase; return this; } @Override public Child build() { Child child = new Child(); child.setRecipient(this.recipient); child.setUseCase(this.useCase); return child; } } public static ChildRequestBuilder childRequestBuilder() { return new ChildRequestBuilder(); } }
内容的提问来源于stack exchange,提问作者Arnav Sengupta

