How does one configure Spring Data Rest to serialize related entities directly?
I want it to look like this: note the "owner" link is to an "account" entity.
{
"name" : "customer",
"_links" : {
"self" : {
"href" : "http://localhost:8081/api/v1/servers/1005"
},
"owner" : {
"href" : "http://localhost:8081/api/v1/account/100"
}
}
}
Currently (the default) has related entities (aka, associations) serialized indirectly.
I DON'T want it to look like this: the "owner" link is via the self server entity.
{
"name" : "customer",
"_links" : {
"self" : {
"href" : "http://localhost:8081/api/v1/servers/1005"
},
"owner" : {
"href" : "http://localhost:8081/api/v1/servers/1005/owner"
}
}
}
I've checked the docs and can't find any mention of going the "direct" route.
Solved with hackage.
Steps:
- Add the
@RestResource(exported = false)
to the association on the entity.
- Register a
ResourceProcessor<Resource<OwnedEntity>>
@Bean
(OwnedEntity is my base class for entities that have an owner) and change the collection of links in that method.
Details are in the Customizing the JSON output section of the Spring Data REST reference docs.
By Request, here's some code that does this:
/*
* Copyright (c) 2017. DataVolo, Inc. All Rights Reserved.
*/
package com.datavolo.tenant.web;
import com.datavolo.tenant.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
import org.springframework.stereotype.Component;
import javax.annotation.Nonnull;
/**
*
*/
@Component
public class AccountResourceAssembler extends ResourceAssemblerSupport<Account, AccountResource> {
private final RepositoryEntityLinks repositoryEntityLinks;
@Autowired
public AccountResourceAssembler(@Nonnull RepositoryEntityLinks repositoryEntityLinks) {
super(AccountController.class, AccountResource.class);
this.repositoryEntityLinks = repositoryEntityLinks;
}
@Override
public AccountResource toResource(Account entity) {
Link accountLink = repositoryEntityLinks.linkToSingleResource(Account.class, entity.getId());
String accountHref = accountLink.getHref();
Link selfLink = new Link(accountHref, Link.REL_SELF);
Link subAccounts = new Link(accountHref + "/subAccounts", "subAccounts");
Link owner = new Link(accountHref + "/owner", "owner");
Account parent = entity.getParent();
Link[] links;
if (parent == null) {
links = new Link[] {selfLink, accountLink, subAccounts, owner};
} else {
Link parentAccountLink = repositoryEntityLinks.linkToSingleResource(Account.class, parent.getId());
Link parentLink = new Link(parentAccountLink.getHref(), "parent");
links = new Link[] {selfLink, accountLink, subAccounts, owner, parentLink};
}
return new AccountResource(entity, links);
}
}
That then gets injected into the Controllers (annotated with @RepositoryRestController
) that, in turn, generate the response.
In this system we have a shared base class for the controllers, and we have a multi-tenant setup where all non-trivial, non-lookup domain objects (e.g., everything that's storing system data) reference either directly or indirectly the account object, which is what controls and represents the tenancy. So we do this in one place for one object and we're done. Other links are more manual and over time we've largely just shrugged and left the default Spring HATEOS output as-is and let the clients adjust to it. We change this only when the default routinely causes multiple round-trips to the backend -- which is the essential problem with that default way Spring has of handling it. But it's a tradeoff. Spring's default is meant to cause no extra overhead when the back-end resource itself is declared as lazily resolved reference. The nice enhancement to that would be to have it be smarter about those resources so that ones that are already fetched are directly referenced by their own id in the REST response.