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

Posted

技术标签:

【中文标题】使用 JSON-B / Yasson 将 JSON 反序列化为多态 POJO【英文标题】:Deserialize JSON into polymorphic POJO with JSON-B / Yasson 【发布时间】:2020-10-05 11:49:44 【问题描述】:

我在资源类中有一个 PATCH 端点,其中一个抽象类作为请求主体。 我收到以下错误:

22:59:30 SEVERE [or.ec.ya.in.Unmarshaller] (on Line: 64) (executor-thread-63) Can't create instance

似乎因为我声明为参数的主体模型是抽象的,所以它无法反序列化。

我希望得到 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】:

JSON-B 还没有对多态反序列化的理想支持,所以你现在最多可以做一些解决方法。我们在这里有一个设计问题:https://github.com/eclipse-ee4j/jsonb-api/issues/147,所以请给它投上+1

您对自定义反序列化器有正确的想法,但问题是您无法解析当前的JsonObject,然后使用相同的解析器调用context.deserialize(),因为解析器已经超过了它读取的状态JSON 对象。 (JSONParser 是一个只进的解析器,所以没有办法“倒回”它)

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

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;
  
    假设您不能或不想更改 JSON 架构,您可以更改 Java 字段名称(startend)以匹配 JSON 字段名称(startTimeendTime ) 或者您可以使用 @JsonbProperty 注释重新映射它们(我在上面已经完成了) 由于您的时间戳采用 ISO_LOCAL_DATE_TIME 格式,我建议使用 java.time.LocalDateTime 而不是 java.sql.Timestamp,因为 LocalDateTime 处理时区,但最终任何一种方式都可以使用您提供的示例数据

【讨论】:

以上是关于使用 JSON-B / Yasson 将 JSON 反序列化为多态 POJO的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JSON-B 将 JSON 字符串反序列化为非公共类?

JSON-B 精选课程,入门级菜鸟必读!

Primefaces 与 Wildfly 中的 Yasson 有啥关系吗?

实用代码片段将json数据绑定到html元素 (转)

使用 Json 反序列化在 Java 中自动将空字符串转换为 null

Spring Boot2(015):JSON