为什么 JsonDeserialize 无法使用 Lombok SuperBuilder 构造 类

Why is JsonDeserialize not able to contruct classes using Lombok SuperBuilder

我正在尝试创建一个表示嵌套设备位置层次结构的对象模型。例如,'deck' 包含一个 'slide tray',后者包含一个或多个 'slides'。我希望能够读取包含系统 hierarchy/configuration 的 json 文件。我想在我的 classes 中使用 Lombok 构建器,这样我就可以在需要时安全地在代码中生成 json 文件。更常见的用例是读入 json 文件以在应用程序启动时创建 pojo。使用生成器生成 json 文件效果很好。但是,我还没有将文件反序列化回 pojo 的。

这是我遇到的错误:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `my.org.Deck$DeckBuilder` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{"type":"Deck","locNumber":1,

顶级超class是这样的:

package my.org;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Getter;
import lombok.Singular;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;

import java.awt.geom.Point2D;
import java.util.List;


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = Deck.class, name = "Deck"),
        @JsonSubTypes.Type(value = SlideTray.class, name = "SlideTray"),
        @JsonSubTypes.Type(value = Slide.class, name = "Slide"),
        @JsonSubTypes.Type(value = NullLoc.class, name = "null"),
})
@SuperBuilder
@Getter
@Accessors(fluent = true, chain = true)
@JsonDeserialize(builder = BaseLocationType.BaseLocationTypeBuilder.class)
public class BaseLocationType<T extends BaseLocationType> {

    @JsonProperty("locNumber")
    private int locNumber;

    @JsonProperty("posRelativeToParent")
    private Point2D.Double positionRelativeToParent;

    @Singular
    @JsonProperty("childLocs")
    private List<T> childLocs;

}

The Deck子class:

package my.org;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@Getter
@EqualsAndHashCode(callSuper = true)
@Accessors(fluent = true, chain = true)
@JsonDeserialize(builder = Deck.DeckBuilder.class)
public class Deck extends BaseLocationType<SlideTray> {

    private String deckField1;

    private String deckField2;


}

SlideTray子class:

package my.org;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@Getter
@EqualsAndHashCode(callSuper = true)
@Accessors(fluent = true, chain = true)
@JsonDeserialize(builder = SlideTray.SlideTrayBuilder.class)
public class SlideTray extends BaseLocationType<Slide> {

    private String slideTrayField1;

}

幻灯片子class:

package my.org;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@Getter
@EqualsAndHashCode(callSuper = true)
@Accessors(fluent = true, chain = true)
@JsonDeserialize(builder = Slide.SlideBuilder.class)
public class Slide extends BaseLocationType<NullLoc> {

    private String slideField1;

}

空位置:

package my.org;

import lombok.experimental.SuperBuilder;

@SuperBuilder
public class NullLoc extends BaseLocationType<NullLoc> {

    // no fields or builder, etc
}

测试代码 - 失败并在 mapper.readValue():

上出现上述异常
// create 1 deck with 1 slideTray that has 2 slides
Deck.DeckBuilder<?, ?> deckBuilder = Deck.builder()
        .locNumber(1)
        .positionRelativeToParent(new Point2D.Double(1.0, 1.0))
        .deckField1("deck f1 data")
        .deckField2("deck f2 data")
        .childLoc(SlideTray.builder()
                .locNumber(2)
                .positionRelativeToParent(new Point2D.Double(2.0, 2.0))
                .slideTrayField1("slide tray f1 data")
                .childLoc(Slide.builder()
                        .locNumber(3)
                        .positionRelativeToParent(new Point2D.Double(3.0, 3.0))
                        .slideField1("child1-slide f1 data")
                        .build())
                .childLoc(Slide.builder()
                        .locNumber(4)
                        .positionRelativeToParent(new Point2D.Double(4.0, 4.0))
                        .slideField1("child2-slide f1 data")
                        .build()).build());

Deck deckPojo = deckBuilder.build();

// serialize the pojo's
String json = new ObjectMapper().writeValueAsString(deckPojo);

// de-serialize the json back into the pojo's
ObjectMapper mapper = new ObjectMapper();
Deck deckPojoDeserialized = mapper.readValue(json, Deck.class);

生成的json:

{
  "type": "Deck",
  "locNumber": 1,
  "posRelativeToParent": {
    "x": 1.0,
    "y": 1.0
  },
  "childLocs": [
    {
      "type": "SlideTray",
      "locNumber": 2,
      "posRelativeToParent": {
        "x": 2.0,
        "y": 2.0
      },
      "childLocs": [
        {
          "type": "Slide",
          "locNumber": 3,
          "posRelativeToParent": {
            "x": 3.0,
            "y": 3.0
          },
          "childLocs": []
        },
        {
          "type": "Slide",
          "locNumber": 4,
          "posRelativeToParent": {
            "x": 4.0,
            "y": 4.0
          },
          "childLocs": []
        }
      ]
    }
  ]
}

注意:我在 Whosebug 中没有看到上传演示项目 zip 文件的选项...但如果需要,我可以想办法分享它。 谢谢!

我认为根本问题与三个主要子 class 中定义的 @JsonDeserialize 注释 builder 值有关,因为它们看起来是 abstract class 参考文献。这也可以解释您收到的错误消息。

来自 Lombok @SuperBuilder 文档 ref:

To ensure type-safety, @SuperBuilder generates two inner builder classes for each annotated class, one abstract and one concrete class named FoobarBuilder and FoobarBuilderImpl (where Foobar is the name of the annotated class).

我相信更新以下 @JsonDeserialize 注释 builder 值将有助于解决问题:

Deck分class:

@JsonDeserialize(builder = Deck.DeckBuilderImpl.class)

SlideTray分class:

@JsonDeserialize(builder = SlideTray.SlideTrayBuilderImpl.class)

Slide分class:

@JsonDeserialize(builder = Slide.SlideBuilderImpl.class)

关于 BuilderImpl 手动更新的补充说明:

@SuperBuilder 文档ref 包含与此主题相关的以下支持信息:

Customizing the code generated by @SuperBuilder is limited to adding new methods or annotations to the builder classes, and providing custom implementations of the 'set', builder(), and build() methods. You have to make sure that the builder class declaration headers match those that would have been generated by lombok. Due to the heavy generics usage, we strongly advice to copy the builder class definition header from the uncustomized delomboked code.