Jackson module to handle abstract aggregate root and its subclasses in Spring Data REST

jackson, spring-data, spring-data-rest

Solution

There is no need to create a Jackson module. To use a single table for inherited entities we can use @RestResource annotation to mark them as the same resources:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "criteria")
public abstract class Criterion extends AbstractEntity {
}

@RestResource(rel = "criteria", path = "criteria")
@Entity
public class NameCriterion extends Criterion {

    private String name;
}

@RestResource(rel = "criteria", path = "criteria")
@Entity
public class TitleCriterion extends Criterion {

    private String title;
}

@RepositoryRestResource(path = "criteria", collectionResourceRel = "criteria", itemResourceRel = "criterion")
public interface CriterionRepository extends JpaRepository<Criterion, Long> {
}

So it becomes possible to obtain all the resources (NameCriterion and TitleCriterion) in one output:

GET http://localhost:8080/api/criteria

{
  "_embedded": {
    "criteria": [
      {
        "name": "name1",
        "_links": {
          "self": {
            "href": "http://localhost:8080/api/criteria/1"
          },
          "nameCriterion": {
            "href": "http://localhost:8080/api/criteria/1"
          }
        }
      },
      {
        "title": "title1",
        "_links": {
          "self": {
            "href": "http://localhost:8080/api/criteria/2"
          },
          "titleCriterion": {
            "href": "http://localhost:8080/api/criteria/2"
          }
        }
      }
    ]
  }
}

GET http://localhost:8080/api/criteria/1
{
  "name": "name1",
  "_links": {
    "self": {
      "href": "http://localhost:8080/api/criteria/1"
    },
    "nameCriterion": {
      "href": "http://localhost:8080/api/criteria/1"
    }
  }
}

GET http://localhost:8080/api/criteria/2
{
  "title": "title1",
  "_links": {
    "self": {
      "href": "http://localhost:8080/api/criteria/2"
    },
    "titleCriterion": {
      "href": "http://localhost:8080/api/criteria/2"
    }
  }
}

Working example.

Problem

I have Spring Data REST based application with repository ``` public interface CriterionRepository extends JpaRepository<Criterion, Long> { } ``` whereas `Criterion` is base class: ``` @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public abstract class Criterion extends AbstractEntity {} ``` and `NameCriterion` is its subclass ``` @Entity public class NameCriterion extends Criterion { private final String name; } ``` Spring Data REST exports the repository as REST resource and one can access it at http://localhost:8080/api/criteria/ Exported resource looks as follows: ``` { "_embedded": { "nameCriteria": [{ "_links": { "self": { "href": "http://localhost:8080/api/nameCriterion/1" }, "nameCriterion": { "href": "http://localhost:8080/api/nameCriterion/1" } } } ] }, "_links": { "self": { "href": "http://localhost:8080/api/criteria" }, "profile": { "href": "http://localhost:8080/api/profile/criteria" } }, "page": { "size": 20, "totalElements": 1, "totalPages": 1, "number": 0 } } ``` When I try to follow self link, there is no mapping for http://localhost:8080/api/nameCriterion/1 I can follow http://localhost:8080/api/criteria/1 though and I get response without name field from `NameCriterion` ``` { "_links": { "self": { "href": "http://localhost:8080/api/nameCriterion/1" }, "nameCriterion": { "href": "http://localhost:8080/api/nameCriterion/1" } } } ``` My assumption is it is a problem with Jackson mapper defined in REST exporter which is not tweaked correctly to handle abstract class `Criterion` used in `JpaRepository` as aggregate root. What Jackson customization should I apply to make it working properly? In other words, what Jackson module should I create?

Original source