使用 JSON-B / Yasson 将 JSON 反序列化为多态 POJO
Deserialize JSON into polymorphic POJO with JSON-B / Yasson
我在资源 class 中有一个 PATCH 端点,其中摘要 class 作为请求 body。
我收到以下错误:
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;
}
这是资源 class 我的端点
@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 请求中发送的请求 body
{
"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>
JSON-B 还没有对多态反序列化的理想支持,因此您现在最多可以做一种解决方法。我们在这里为它打开了一个设计问题:https://github.com/eclipse-ee4j/jsonb-api/issues/147,所以请给它投票 +1
!
您对自定义反序列化器的想法是正确的,但问题是您无法解析当前的 JsonObject
然后稍后使用相同的解析器调用 context.deserialize()
,因为解析器已经提前越过它读取 JSON 对象的状态。 (JSON解析器是一个前向解析器,所以没有办法 "rewind" 它)
因此,您仍然可以在自定义反序列化器中调用 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;
}
- 假设您不能或不想更改 JSON 架构,您可以更改 java 字段名称(
start
和 end
)匹配 JSON 字段名称(startTime
和 endTime
)或者您可以使用 @JsonbProperty
注释重新映射它们(我在上面已经完成)
- 由于您的时间戳采用 ISO_LOCAL_DATE_TIME 格式,我建议使用
java.time.LocalDateTime
而不是 java.sql.Timestamp
因为 LocalDateTime
处理时区,但最终两种方式都可以根据您提供的示例数据
我在资源 class 中有一个 PATCH 端点,其中摘要 class 作为请求 body。 我收到以下错误:
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;
}
这是资源 class 我的端点
@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 请求中发送的请求 body
{
"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>
JSON-B 还没有对多态反序列化的理想支持,因此您现在最多可以做一种解决方法。我们在这里为它打开了一个设计问题:https://github.com/eclipse-ee4j/jsonb-api/issues/147,所以请给它投票 +1
!
您对自定义反序列化器的想法是正确的,但问题是您无法解析当前的 JsonObject
然后稍后使用相同的解析器调用 context.deserialize()
,因为解析器已经提前越过它读取 JSON 对象的状态。 (JSON解析器是一个前向解析器,所以没有办法 "rewind" 它)
因此,您仍然可以在自定义反序列化器中调用 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;
}
- 假设您不能或不想更改 JSON 架构,您可以更改 java 字段名称(
start
和end
)匹配 JSON 字段名称(startTime
和endTime
)或者您可以使用@JsonbProperty
注释重新映射它们(我在上面已经完成) - 由于您的时间戳采用 ISO_LOCAL_DATE_TIME 格式,我建议使用
java.time.LocalDateTime
而不是java.sql.Timestamp
因为LocalDateTime
处理时区,但最终两种方式都可以根据您提供的示例数据