我想設置一個基於REST的Spring引導應用程序,其中我有一個POST請求接受SubscriptionProduct對象。 SubscriptionProduct類有一個Services列表,其中Services是一個包含兩個子類的抽象類; OnDemandService和LiveService。應用程序無法反序列化傳入的JSON對象。傑克遜反序列化一個抽象類的子類的對象列表
抽象基類已用我認爲必要的傑克遜註釋註釋。
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = false)
@JsonSubTypes({
@JsonSubTypes.Type(value = LiveLinearServiceView.class, name = "LIVE"),
@JsonSubTypes.Type(value = OnDemandServiceView.class, name = "ONDEMAND")
})
當我嘗試運行deserialisation,我收到以下錯誤信息:
13:34:09.000 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [[email protected] testClass = SerializationTest, testInstance = [email protected], testMethod = [email protected], testException = com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of no.acme.domain.service.Service: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: {
"type" : "SUBSCRIPTION",
"id" : null,
"type" : "SUBSCRIPTION",
"name" : "Test Product",
"services" : [ {
"type" : "LIVE",
"id" : null,
"type" : "LIVE",
"name" : "Test Service",
"logoURL" : "http://localhost:8080/test.png"
} ]
}; line: 6, column: 18] (through reference chain: no.acme.dto.product.SubscriptionProductView["services"]->java.util.ArrayList[0]), mergedContextConfiguration = [[email protected] testClass = SerializationTest, locations = '{}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[org.springfr[email protected]3901d134, org.[email protected]0, org.springframework.boot[email protected]0, org.springframework.boot.test.autocon[email protected]794cb805], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]], class annotated with @DirtiesContext [false] with mode [null], method annotated with @DirtiesContext [false] with mode [null].
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of no.acme.domain.service.Service: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: {
"type" : "SUBSCRIPTION",
"id" : null,
"type" : "SUBSCRIPTION",
"name" : "Test Product",
"services" : [ {
"type" : "LIVE",
"id" : null,
"type" : "LIVE",
"name" : "Test Service",
"logoURL" : "http://localhost:8080/test.png"
} ]
}; line: 6, column: 18] (through reference chain: no.acme.dto.product.SubscriptionProductView["services"]->java.util.ArrayList[0])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1456)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:149)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:287)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:259)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:504)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:104)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:178)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:150)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:129)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1082)
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:63)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842)
at no.acme.SerializationTest.testSerializeAndDeserializeSubscriptionProduct(SerializationTest.java:57)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
我創建了一個小的測試項目spring-boot-deserialization-issue與測試情況下,其顯示情況下,deserialisation問題。
測試用例
@Test
public void testSerializeAndDeserializeSubscriptionProduct() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
mapper.registerModule(new JavaTimeModule());
LiveService service = new LiveService("Test Service", new URL("http://localhost:8080/test.png"));
List<Service> services = new ArrayList<>();
services.add(service);
SubscriptionProduct product = new SubscriptionProduct("Test Product");
product.setServices(services);
SubscriptionProductView productView = new SubscriptionProductView(product);
String jsonString = mapper.writeValueAsString(productView);
log.info(jsonString);
SubscriptionProductView mappedProductView = mapper.readValue(jsonString, SubscriptionProductView.class);
Assert.assertEquals("Test Product", mappedProductView.name);
Assert.assertEquals(1, mappedProductView.services.size());
}
要運行測試
MVN測試
SubscriptionProductView類 這是被張貼到REST API的對象,其中有服務對象列表TS。
public class SubscriptionProductView extends ProductView {
public List<ServiceView> services;
protected SubscriptionProductView() {
super();
}
public SubscriptionProductView(SubscriptionProduct product) {
super(product);
this.setServices(product.getServices());
}
public void setServices(List<Service> services) {
this.services = new ArrayList<>();
for (Service service : services) {
switch (service.getType()) {
case ONDEMAND:
this.services.add(new OnDemandServiceView((OnDemandService)service));
break;
case LIVE:
this.services.add(new LiveLinearServiceView((LiveService)service));
break;
default:
throw new IllegalArgumentException("Unknown service type: " + service.getType());
}
}
}
}
ServiceView類 抽象基類批註的東西我認爲是必要的傑克遜註釋。
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = false)
@JsonSubTypes({
@JsonSubTypes.Type(value = LiveLinearServiceView.class, name = "LIVE"),
@JsonSubTypes.Type(value = OnDemandServiceView.class, name = "ONDEMAND")
})
public abstract class ServiceView {
public Long id;
@Enumerated(EnumType.STRING)
public ServiceType type;
public String name;
protected ServiceView() {
}
@JsonCreator
public ServiceView(@JsonProperty("id")Long id, @JsonProperty("type")ServiceType type, @JsonProperty("name")String name) {
this.id = id;
this.type = type;
this.name = name;
}
public ServiceView(Service service) {
this.id = service.getId();
this.type = service.getType();
this.name = service.getName();
}
}
LiveLinearServiceView類 具體ServiceView類,我需要映射。
public class LiveLinearServiceView extends ServiceView {
public String logoURL;
protected LiveLinearServiceView() {
super();
}
@JsonCreator
public LiveLinearServiceView(@JsonProperty("id")Long id, @JsonProperty("type")ServiceType type, @JsonProperty("name")String name, @JsonProperty("logoURL")String logoURL) {
super(id, type, name);
this.logoURL = logoURL;
}
public LiveLinearServiceView(LiveService service) {
super(service);
this.logoURL = service.getLogoURL().toString();
}
}
我的主要問題是,爲什麼這個反序列化不起作用?還有一個額外的問題,爲什麼輸出中會生成兩次「type」參數?
你是正確的地圖,我愚蠢的疏忽。添加@JsonSetter public void setServices(List services){ this。服務=服務; }修復SubscriptionProductView類問題! –