2017-07-17 114 views
1

我想設置一個基於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」參數?

回答

0

添加下面的方法到SubscriptionProductView解決問題

@JsonSetter 
public void setServices(List<ServiceView> services) { 
    this.services = services; 
} 

的問題是有作爲作者Abhijit指出,傑克遜不知道如何將服務使用

public void setServices(List<Service> services) {} 
1

SubscriptionProductView.setServices參考文獻List<Service>;傑克遜不知道如何實例化抽象類列表。有關解決方案,請參閱this

順便說一句,+1在問題和實際代碼中的好的細節;這是如何提出問題的。

+0

你是正確的地圖,我愚蠢的疏忽。添加@JsonSetter public void setServices(List services){ this。服務=服務; }修復SubscriptionProductView類問題! –