使用JSON-B / Yasson将JSON反序列化为多态POJO

6
我在一个资源类中有一个PATCH端点,其请求体为一个抽象类。我收到了以下错误:
22:59:30 SEVERE [or.ec.ya.in.Unmarshaller] (on Line: 64) (executor-thread-63) Can't create instance

似乎因为我声明为参数的body模型是抽象的,所以反序列化失败。

我期望获得Element_A或Element_B中的任意一个。

如何声明body为多态?

这是元素层次结构。

public abstract class BaseElement {
    public String name;
    public Timestamp start;
    public Timestamp end;
}

public class Element_A extends BaseElement{
    public String A_data;
}

public class Element_B extends BaseElement{
    public long B_data;
}

这是我的终端节点所用的资源类。

@Path("/myEndpoint")
public class ResourceClass {

    @PATCH
    @Path("/{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    public Response updateEvent(@Valid BaseElement element, @Context UriInfo uriInfo, @PathParam long id) {
        if (element instanceof Element_A) {
            // Element_A logic
        } else if (element instanceof Element_B) {
            // Element_B logic
        }
        return Response.status(Response.Status.OK).entity("working").type(MediaType.TEXT_PLAIN).build();
    }
}

这是我在PATCH请求中发送的请求主体

{
  "name": "test",
  "startTime": "2020-02-05T17:50:55",
  "endTime": "2020-02-05T17:51:55",
  "A_data": "it's my data"
}

我还尝试使用自定义反序列化器添加@JsonbTypeDeserializer,但未成功

@JsonbTypeDeserializer(CustomDeserialize.class)
public abstract class BaseElement {
    public String type;
    public String name;
    public Timestamp start;
    public Timestamp end;
}

public class CustomDeserialize implements JsonbDeserializer<BaseElement> {

    @Override
    public BaseElement deserialize(JsonParser parser, DeserializationContext context, Type rtType) {
        JsonObject jsonObj = parser.getObject();
        String type = jsonObj.getString("type");

        switch (type) {
            case "A":
                return context.deserialize(Element_A.class, parser);
            case "B":
                return context.deserialize(Element_B.class, parser);
        }

        return null;
    }
}

这是我发送的新请求:

{
  "type": "A"
  "name": "test",
  "startTime": "2020-02-05T17:50:55",
  "endTime": "2020-02-05T17:51:55",
  "A_data": "it's my data"
}


抛出此错误:
02:33:10 SEVERE [or.ec.ya.in.Unmarshaller] (executor-thread-67) null
02:33:10 SEVERE [or.ec.ya.in.Unmarshaller] (executor-thread-67) Internal error: null

我的pom.xml包括:

<dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
1个回答

13

JSON-B 目前对于多态反序列化的支持不够完善,所以目前最好能做到的是通过某种方式进行解决。我们在这里针对这个问题已经开放了一个设计问题:https://github.com/eclipse-ee4j/jsonb-api/issues/147,请给它一个+1来投票支持!

你的自定义反序列化器主意是正确的,但问题在于你不能先解析当前的JsonObject,然后稍后再使用相同的解析器调用context.deserialize(),因为解析器已经超过读取 JSON 对象的状态。(JSONParser 是一种向前的解析器,所以没有“回溯”它的方法)

因此,在自定义反序列化器中仍然可以调用parser.getObject(),但之后需要使用单独的Jsonb实例来解析该特定 JSON 字符串,一旦确定了其具体类型。

public static class CustomDeserialize implements JsonbDeserializer<BaseElement> {

  private static final Jsonb jsonb = JsonbBuilder.create();

  @Override
  public BaseElement deserialize(JsonParser parser, DeserializationContext context, Type rtType) {

      JsonObject jsonObj = parser.getObject();
      String jsonString = jsonObj.toString();
      String type = jsonObj.getString("type");

      switch (type) {
        case "A":
          return jsonb.fromJson(jsonString, Element_A.class);
        case "B":
          return jsonb.fromJson(jsonString, Element_B.class);
        default:
          throw new JsonbException("Unknown type: " + type);
      }
  }
}

副笔:您需要将基本对象模型更改为以下内容:

  @JsonbTypeDeserializer(CustomDeserialize.class)
  public abstract static class BaseElement {
    public String type;
    public String name;
    @JsonbProperty("startTime")
    public LocalDateTime start;
    @JsonbProperty("endTime")
    public LocalDateTime end;
  }
  1. 假设您不能或不想更改JSON架构,您可以将Java字段名称( start end )更改为匹配JSON字段名称( startTime endTime ),或者您可以使用 @JsonbProperty 注释来重新映射它们(我在上面已经这样做了)
  2. 由于您的时间戳采用ISO_LOCAL_DATE_TIME格式,我建议使用 java.time.LocalDateTime 而不是 java.sql.Timestamp ,因为 LocalDateTime 处理时区,但基本上无论哪种方式都可以在提供的示例数据中正常工作

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接