Spring Data REST How to add embedded resources inline

codesmith picture codesmith · Jun 29, 2016 · Viewed 9k times · Source

I'm using Spring Data REST and Hateoas in combination with HAL browser. This works perfectly, but now I would like to make a JSON dump of a specific entity with (a set of) its associated objects. I used @Projection but then I got stuck again.

FYI: The normal behaviour (with embedded and links etc) should remain besides the new endpoint (without embedded and links).

To further illustrate my problem/question:

class Person {
  String name;
  List<Company> companies;
}

class Company {
  String name;
  Address address;
}

class Address {
  String street;
}

Now I would like to see something like this:

{
   "name": "John",
   "companies": [
        {
            "name": "Stackoverflow",
            "address": {"street": "Highway blvd."}
        },
        {
            "name": "Oracle",
            "address": {"street": "Main rd."}
        }
   ]
}

While I'm getting this:

{
   "name": "John",
   "_links": {
        "self": {"href": "http...."},
        "companies": {"href": "http ..."}
   },
}

See also: http://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts

In my example I introduced two difficulties I have: Lists (companies) and multiple levels: person->company->address. Both are required to work (probably 5 levels, some of which have 'many' relations).

Answer

Will Faithfull picture Will Faithfull · Jul 3, 2016

The accepted method of inlining entities is projections, as you identified. Projections are always inlined, so one option is to create projections for each of your entities and combine them like so:

@Projection(name = "personProjection", types = Person.class)
public interface PersonProjection {

    String getFirstName();
    List<CompanyProjection> getCompanies();

}

@Projection(name = "companyProjection", types = Company.class)
public interface CompanyProjection {

    String getName();
    AddressProjection getAddress();

}

@Projection(name = "addressProjection", types = Address.class)
public interface AddressProjection {

    String getStreet();

}

A GET people/1?projection=personProjection will still render _links elements, but you will get the nesting you want:

{
  "companies" : [ {
    "address" : {
      "street" : "123 Fake st",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/addresses/1{?projection}",
          "templated" : true
        }
      }
    },
    "name" : "ACME inc.",
    "_links" : {
      "self" : {
        "href" : "http://localhost:8080/companies/1{?projection}",
        "templated" : true
      },
      "address" : {
        "href" : "http://localhost:8080/companies/1/address"
      }
    }
  } ],
  "firstName" : "Will",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/1"
    },
    "person" : {
      "href" : "http://localhost:8080/people/1{?projection}",
      "templated" : true
    },
    "companies" : {
      "href" : "http://localhost:8080/people/1/companies"
    }
  }
}

Alternatively, if you don't need to expose the Company and Address entities as rest resources, you can mark their repositories with @RepositoryRestResource(exported=false), and they will be inlined wherever they are referenced, without any need for projections.

A final caveat, though - this request is somewhat fighting against the ethos of Spring Data REST and Spring HATEOAS, and inviting big, unwieldy queries suffering the n+1 problem. Remember that Spring Data REST is not a turnkey solution for turning a domain model into an API, and rendering deep object graphs (if that is your intention) is potentially something you might expose as a custom controller endpoint on an ad-hoc basis where you can control the conditions thoroughly.