3. Domain Object Representations

3.1 Links as First-Class Objects

Links are an essential part of RESTful resources and allow for easy discoverability of related resources. In Spring Data REST, a link is represented in JSON as an object with a rel and href property. These objects will appear in an array under an object's links property. These objects are meant to provide a user agent with the URLs necessary to retrieve resources related to the current resource being accessed.

When accessing the root of a Spring Data REST application, for example, links are provided to each repository that is exported. The user agent can then pick the link it is interested in and follow thathref. Issue a get in the rest-shell to see an example of links.

http://localhost:8080:> get
> GET http://localhost:8080/

< 200 OK
< Content-Type: application/json
<
{
  "links" : [ {
    "rel" : "people",
    "href" : "http://localhost:8080/people"
  }, {
    "rel" : "profile",
    "href" : "http://localhost:8080/profile"
  }, {
    "rel" : "customer",
    "href" : "http://localhost:8080/customer"
  }, {
    "rel" : "order",
    "href" : "http://localhost:8080/order"
  }, {
    "rel" : "product",
    "href" : "http://localhost:8080/product"
  } ],
  "content" : [ ]
}

3.1.1 Entity Relationships

If two entities are related to one another through a database-defined relationship, then that relationship will appear in the JSON as a link. In JPA, one would place a @ManyToOne, @OneToOne, or other relationship annotation. If using Spring Data MongoDB, one would place a @DBRef annotation on a property to denote its special status as a reference to other entities. In the example project, the Person class has a related set of Person entities in the siblings property. If you get the resource of a Person you will see, in the siblings property, the link to follow to get the related Persons.

http://localhost:8080:> get people/1
> GET http://localhost:8080/people/1

< 200 OK
< Content-Type: application/json
<
{
  "firstName" : "Billy Bob",
  "surname" : "Thornton",
  "links" : [ {
    "rel" : "self",
    "href" : "http://localhost:8080/people/1"
  }, {
    "rel" : "people.person.father",
    "href" : "http://localhost:8080/people/1/father"
  }, {
    "rel" : "people.person.siblings",
    "href" : "http://localhost:8080/people/1/siblings"
  } ]
}

3.2 Object Mapping

Spring Data REST returns a representation of a domain object that corresponds to the requested Accept type specified in the HTTP request. [1]

Sometimes the behavior of the Spring Data REST's ObjectMapper, which has been specially configured to use intelligent serializers that can turn domain objects into links and back again, may not handle your domain model correctly. There are so many ways one can structure your data that you may find your own domain model isn't being translated to JSON correctly. It's also sometimes not practical in these cases to try and support a complex domain model in a generic way. Sometimes, depending on the complexity, it's not even possible to offer a generic solution.

3.2.1 Adding custom (de)serializers to Jackson's ObjectMapper

To accommodate the largest percentage of use cases, Spring Data REST tries very hard to render your object graph correctly. It will try and serialize unmanaged beans as normal POJOs and it will try and create links to managed beans where that's necessary. But if your domain model doesn't easily lend itself to reading or writing plain JSON, you may want to configure Jackson's ObjectMapper with your own custom type mappings and (de)serializers.

Abstract class registration

One key configuration point you might need to hook into is when you're using an abstract class (or an interface) in your domain model. Jackson won't know by default what implementation to create for an interface. Take the following example:

@Entity
public class MyEntity {
  @OneToMany
  private List<MyInterface> interfaces;
}

In a default configuration, Jackson has no idea what class to instantiate when POSTing new data to the exporter. This is something you'll need to tell Jackson either through an annotation, or, more cleanly, by registering a type mapping using a Module.

To add your own Jackson configuration to the ObjectMapper used by Spring Data REST, override the configureJacksonObjectMapper method. That method will be passed an ObjectMapper instance that has a special module to handle serializing and deserializing PersistentEntitys. You can register your own modules as well, like in the following example.

  @Override protected void configureJacksonObjectMapper(ObjectMapper objectMapper) {
    objectMapper.registerModule(new SimpleModule("MyCustomModule"){
      @Override public void setupModule(SetupContext context) {
        context.addAbstractTypeResolver(
          new SimpleAbstractTypeResolver().addMapping(MyInterface.class,
                                                      MyInterfaceImpl.class)
        );
      }
    });
  }

Once you have access to the SetupContext object in your Module, you can do all sorts of cool things to configure Jacskon's JSON mapping. You can read more about how Modules work on Jackson's wiki: http://wiki.fasterxml.com/JacksonFeatureModules

Adding custom serializers for domain types

If you want to (de)serialize a domain type in a special way, you can register your own implementations with Jackson's ObjectMapper and the Spring Data REST exporter will transparently handle those domain objects correctly. To add serializers, from your setupModule method implementation, do something like the following:

@Override public void setupModule(SetupContext context) {
  SimpleSerializers serializers = new SimpleSerializers();
  SimpleDeserializers deserializers = new SimpleDeserializers();

  serializers.addSerializer(MyEntity.class, new MyEntitySerializer());
  deserializers.addDeserializer(MyEntity.class, new MyEntityDeserializer());

  context.addSerializers(serializers);
  context.addDeserializers(deserializers);
}


[1] Currently, only JSON representations are supported. Other representation types can be supported in the future by adding an appropriate converter and updating the controller methods with the appropriate content-type.