Десериализовать JSON в полиморфный POJO с помощью JSON-B / Yasson
У меня есть конечная точка PATCH в классе ресурсов с абстрактным классом в качестве тела запроса. Я получаю следующую ошибку:
22:59:30 SEVERE [or.ec.ya.in.Unmarshaller] (on Line: 64) (executor-thread-63) Can't create instance
Похоже, что из-за того, что модель тела, которую я объявляю в качестве аргумента, является абстрактной, она не может десериализоваться.
Я ожидаю получить Element_A или Element_B
Как объявить тело полиморфным?
Это иерархия элементов
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()
в вашем пользовательском десериализаторе, но тогда вам нужно использовать отдельный 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
обрабатывает часовые пояса, но в конечном итоге будет работать в любом случае, учитывая предоставленные вами образцы данных