Bert
Bert

Reputation: 930

Polymorphism with Spring Hateoas

After trying several variants, I'm stuck in trying to make my Spring HATEOAS controller do polymorphism.

My first variant was to implement the resources as instances of Resource, with my object as content. The base class is defined as follows:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type")
@JsonSubTypes({ @Type(value = Cat.class, name = "Cat"), @Type(value = Dog.class, name = "Dog") })
@JsonRootName("Animal")
public abstract class Animal
{
  :
  :
}

When fetching a single instance or a page, the following exception is thrown:

Could not write content: Unwrapped property requires use of type information: can not serialize without disabling `SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS`

The full repro is available here: https://github.com/Bert-R/spring-hateoas-polymorphism/tree/master/src/main/java/nu/famroos/spring/hateoas/polymorphism/repro1

My understanding is that this error is caused by an @JsonUnwrapped annotation on the getContent method of Resource. The PagedResources apparently uses a similar approach.

My second attempt was to make my classes inherit from ResourceSupport, to circumvent the @JsonUnwrapped issue. The base class now looks like this:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type")
@JsonSubTypes({ @Type(value = Cat2.class, name = "Cat2"), @Type(value = Dog2.class, name = "Dog2") })
@JsonRootName("Animal2")
public abstract class Animal2 extends ResourceSupport
{
  :
  :
}

This works partially. A single resource is now serialized correctly (note the @type property):

{
   "@type":"Dog2",
   "name":"nameofdog",
   "barkVolume":1.0,
   "_links":{
      "self":{
         "href":"http://localhost:8082/animals2/nameofdog"
      }
   }
}

However, a page does not have the @type property:

{
   "_embedded":{
      "Animal2s":[
         {
            "name":"nameofdog",
            "barkVolume":1.0,
            "_links":{
               "self":{
                  "href":"http://localhost:8082/animals2/nameofdog"
               }
            }
         },
         :
         :
      ]
   },
   "_links":{
      "self":{
         "href":"http://localhost:8082/animals2"
      }
   },
   "page":{
      "size":20,
      "totalElements":3,
      "totalPages":1,
      "number":0
   }
}

The code of this repro is available here: https://github.com/Bert-R/spring-hateoas-polymorphism/tree/master/src/main/java/nu/famroos/spring/hateoas/polymorphism/repro2

I would very much appreciate if someone could explain a way to make polymorphism work in combination with Spring HATEOAS.

Upvotes: 2

Views: 2088

Answers (1)

Bert
Bert

Reputation: 930

After looking further, I found out the reason why the @type attribute is not included in a page of resources: type erasure. See this issue.

There is a workaround for this: Instead of include = JsonTypeInfo.As.PROPERTY, use include = JsonTypeInfo.As.EXISTING_PROPERTY and add a property to each subclass that returns the type. It's not elegant, but it allows to implement a polymorphic API. The full source code for this workaround is available here: https://github.com/Bert-R/spring-hateoas-polymorphism/tree/master/src/main/java/nu/famroos/spring/hateoas/polymorphism/workaround

P.S. For those who feel that disabling SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS would solve the issue in the first variant: if you disable that setting, the type information will be ignored, so the @type attribute won't be added during serialization.

Upvotes: 3

Related Questions