title: Jackson deserialise a list of objects of subclasses of an abstract class
body: <p>I am trying to setup a REST based Spring Boot application, where I have a POST request accepting SubscriptionProduct objects. The SubscriptionProduct class has a list of Services, where Services is an abstract class with two sub-classes; OnDemandService and LiveService. The application fails to deserialise the incoming JSON object. </p>
  
  <p>The abstract base class have been annotated with what I thought were the necessary Jackson annotations.</p>
  
  <pre><code>@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")
  })
  </code></pre>
  
  <p>When I try to run the deserialisation, I get the following error message:</p>
  
  <pre><code>13:34:09.000 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext@7f3b84b8 testClass = SerializationTest, testInstance = no.acme.SerializationTest@1b7cc17c, testMethod = testSerializeAndDeserializeSubscriptionProduct@SerializationTest, 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"]-&gt;java.util.ArrayList[0]), mergedContextConfiguration = [MergedContextConfiguration@57a3af25 testClass = SerializationTest, locations = '{}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@3901d134, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@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"]-&gt;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)
  </code></pre>
  
  <p>I have created a small test project <a href="https://github.com/habakke/spring-boot-deserialization-issue" rel="nofollow noreferrer">spring-boot-deserialization-issue</a> with a test case which show case the deserialisation issue.</p>
  
  <p><strong>Test case</strong></p>
  
  <pre><code>@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&lt;Service&gt; services = new ArrayList&lt;&gt;();
      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());
  }
  </code></pre>
  
  <p>To run the test </p>
  
  <blockquote>
    <p>mvn test</p>
  </blockquote>
  
  <p><strong>SubscriptionProductView class</strong>
  This is the object which is posted to the REST API, which has the list of service objects. </p>
  
  <pre><code>public class SubscriptionProductView extends ProductView {
  
      public List&lt;ServiceView&gt; services;
  
      protected SubscriptionProductView() {
          super();
      }
  
      public SubscriptionProductView(SubscriptionProduct product) {
          super(product);
          this.setServices(product.getServices());
      }
  
      public void setServices(List&lt;Service&gt; services) {
          this.services = new ArrayList&lt;&gt;();
          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());
              }
          }
      }
  }
  </code></pre>
  
  <p><strong>ServiceView class</strong>
  The abstract base class have been annotated with what I thought were the necessary Jackson annotations.</p>
  
  <pre><code>@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();
      }
  }
  </code></pre>
  
  <p><strong>LiveLinearServiceView class</strong>
  The concrete ServiceView class, which I need mapped.</p>
  
  <pre><code>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();
      }
  }
  </code></pre>
  
  <p>My my main question is, why is this deserialisation not working? And a bonus question, why is the "type" parameter generated twice in the output?</p>