16

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).

codesmith
  • 1,381
  • 3
  • 20
  • 42
  • You can by not using Spring Data REST. Spring Data Rest takes an opinionated view on how to do rest right, and that is including links for the relations instead of the full entity. – M. Deinum Jun 29 '16 at 11:19
  • Thank you Deinum. Spring Data Rest is on the classpath and must remain there. Is you comment still applicable then? – codesmith Jun 29 '16 at 15:21
  • other ways to embed values of linked objects: https://stackoverflow.com/a/52217253/2248405 – robie2011 Sep 07 '18 at 07:19
  • I have this behavior in my application after updating to Spring Boot 2.0.5. Before this migration, any nested entity was listed as inline as default. Why did this happended? Anyone knows? – silviagreen Apr 26 '23 at 08:22

3 Answers3

21

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.

Will Faithfull
  • 1,868
  • 13
  • 20
  • Is there any way to configure or override some Spring beans to get it in-line all eager relations, like it USED to do. Without using the clumsy projections and will have exported repose for the nested entities ? – user3852017 Aug 11 '21 at 10:52
0

In the second endpoint , if you don't want links , you have to have a controller and a resource , where you map the data to the resource and return the collection of resource from the controller

Fahad Fazil
  • 638
  • 1
  • 9
  • 20
  • Thank you Fahad, but can you please illustrate what you mean? Do you mean a `@Controller` and a `@Resource` or more in general? How does this mapping of data work in you view? – codesmith Jun 29 '16 at 15:22
  • By resource I mean , a class that is similar to the entity with the information you want . You can you dozer bean mapper to map the data to the resource . – Fahad Fazil Jun 29 '16 at 15:30
  • It might then get problematic when relations also point the other way (from Address to Company and from Company to Person. The non-spring-data-rest approach will show everything (infinite recursive loop). `@JsonIgnore` is not an option in this case, because then the hatoas-view will render incorrectly. I haven't tested this yet, but that seems logical to me. – codesmith Jun 30 '16 at 12:19
-1

I believe the best way to embed a List or a HashMap is via converting it into a json string and back to java object when you read it....

this can be easily be done using

    @Convert(converter = PluginAnalyzerConfigConverter.class)
  private PluginAnalyzerConfig configuration;
//in Entity Class

// declare a converter class like this
public class PluginAnalyzerConfigConverter implements
    AttributeConverter<PluginAnalyzerConfig, String> {

  @Override public String convertToDatabaseColumn(PluginAnalyzerConfig config) {
    Gson parser = new Gson();
    return parser.toJson(config, PluginAnalyzerConfig.class);
  }

  @Override public PluginAnalyzerConfig convertToEntityAttribute(String source) {
    Gson parser = new Gson();
    return parser.fromJson(source, PluginAnalyzerConfig.class);
  }
}

as shown on Spring Data with Mysql JSON type

We have similar thing in Spring Data DynamoDb - for AWS DynamoDB

Abdeali Chandanwala
  • 8,449
  • 6
  • 31
  • 45