You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jackson无法识别父类字段:子类反序列化报错问题

解决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识别,从而抛出异常。

具体修复步骤

  1. 替换普通@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 {
        // 字段和其他内容不变
    }
    
  2. 优化测试代码(可选但推荐)
    修复后可直接通过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());
    }
    
  3. 备选方案(不推荐)
    若无法升级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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 22:12:10