28

I'm using Spring Boot and HATEOAS to build a REST API and when my API returns a collection, it is wrapped inside a "_embedded" property, like so:

{
   "_links":{
      "self":{
         "href":"http://localhost:8080/technologies"
      }
   },
   "_embedded":{
      "technologies":[
         {
            "id":1,
            "description":"A",
            "_links":{
               "self":{
                  "href":"http://localhost:8080/technologies/1"
               }
            }
         },
         {
            "id":2,
            "description":"B",
            "_links":{
               "self":{
                  "href":"http://localhost:8080/technologies/2"
               }
            }
         }
      ]
   }
}

I want the response to be like this:

{
   "_links":{
      "self":{
         "href":"http://localhost:8080/technologies"
      }
   },
   "technologies":[
      {
         "id":1,
         "description":"A",
         "_links":{
            "self":{
               "href":"http://localhost:8080/technologies/1"
            }
         }
      },
      {
         "id":2,
         "description":"B",
         "_links":{
            "self":{
               "href":"http://localhost:8080/technologies/2"
            }
         }
      }
   ]
}

My TechnologiesController:

@RestController
@ExposesResourceFor(Technology.class)
@RequestMapping(value = "/technologies")
public class TechnologiesController {
    ...
    @ResquestMapping(method = RequestMethod.GET, produces = "application/vnd.xpto-technologies.text+json")
    public Resources<Resource<Technology>> getAllTechnologies() {
        List<Technology> technologies = technologyGateway.getAllTechnologies();
        Resources<<Resource<Technology>> resources = new Resources<Resource<Technology>>(technologyResourceAssembler.toResources(technologies));
        resources.add(linkTo(methodOn(TechnologiesController.class).getAllTechnologies()).withSelfRel());
        return resources;
    }

The configuration class has the annotation @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL).

What is the best way to produce the response without the "_embedded"?

jose_p
  • 624
  • 1
  • 8
  • 12
  • 2
    If you remove `_embedded` from the response then the response will no longer be valid HAL. You either need to stick with `_embedded` or use a different media type. – Andy Wilkinson Mar 02 '15 at 12:24
  • 3
    The HAL draft says "The reserved "_embedded" property is OPTIONAL" – jose_p Mar 02 '15 at 14:56
  • 4
    It's optional in that a resource doesn't have to have any embedded resources. However, if it does, then they should be beneath `_embedded`. – Andy Wilkinson Mar 02 '15 at 15:02
  • 1
    I'm having this same issue. I have a projection on an object, and am limiting it to only display the name. The problem is that there are over 20 relations, so the _embedded object is huge. I haven't found a good way to overcome this either. – aglassman Oct 05 '15 at 16:28

7 Answers7

10

As the documentation says

application/hal+json responses should be sent to requests that accept application/json

In order to omit _embedded in you response you'll need to add

spring.hateoas.use-hal-as-default-json-media-type=false

to application.properties.

ksokol
  • 8,035
  • 3
  • 43
  • 56
10

I close HAL feature, because it is hard to using Resources/Resource by restTemplate. I disable this feature by following code:

public class SpringRestConfiguration implements RepositoryRestConfigurer {
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {

        config.setDefaultMediaType(MediaType.APPLICATION_JSON);
        config.useHalAsDefaultJsonMediaType(false);
    }
}

It work for me. HAL is good if there are more support with restTemplate.

shenyu1997
  • 602
  • 7
  • 5
  • it works but actually doesnt solve the problem at all. Instead of returning the data inside `_embedded` field it creates a `content` field which is better but doesnt create the `technologies` field required by the author; – Manoel Stilpen Nov 08 '21 at 23:15
7

Adding this Accept header to the request:

Accept : application/x-spring-data-verbose+json
Smajl
  • 7,555
  • 29
  • 108
  • 179
3

For those who use Spring Data, and consider it as a problem - solution is to set

spring.data.rest.defaultMediaType = application/json

in application properties. There still links will be available, but no _embedded any more.

Uwe Allner
  • 3,399
  • 9
  • 35
  • 49
Stepan Mozyra
  • 212
  • 2
  • 6
3

What you're describing in the produced and expected results are semantically different things. The former thing is the HAL representation of a Collection<Technology>. The latter, which you expect is the representation of:

class Wrapper {
  Resources<Technology> technologies;
}

Note how this is how we actually create the top level technologies property that you would like to see in your response. You don't create any of the latter in your controller. A top-level Resourcesinstance is basically a collection and the only way to represent a top-level collection in HAL is _embedded. Apparently you don't want that but that's what you have written in your controller method.

Assuming you have Wrapper, something like this should work (untested):

Wrapper wrapper = new Wrapper(assembler.toCollectionModel(technologies);
EntityModel<Wrapper> model = EntityModel.of(wrapper);
model.add(linkTo(…));

PS: As of Spring HATEOAS 1.0, Resources is CollectionModel and Resourceis EntityModel.

Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
  • This answer, though untested, goes beyond a configuration change that doesn't address the problem. Thank you. – Keenan Apr 14 '23 at 19:07
0

You can use this code in the service

  constructor(
    private httpClient: HttpClient
  ) { }

  retrieveAllStudents(){
    return this.httpClient.get<any[]>(`http://localhost:8080/students`);
  }

This will deal with the _embedded part of Json and extract the desired data.

export class ListStudentsComponent implements OnInit {

 // declaring variables to be used
  student: Student;
  students: Student[];
  message: string;

  // injecting student service into the constuctor
   constructor(
    private studentService: StudentService,
  ) { }

  ngOnInit() {
    this.refreshStudents();
  }
refreshStudents(){
  this.studentService.retrieveAllStudents().subscribe(
     response => {
       console.log(response);
      this.students = response._embedded.students as Student[];
     }
   );
 }
Anosh
  • 9
  • 1
0

For latest versions in Spring RepositoryRestConfigurer doesn't include the method public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) you'd need to override the default method on RepositoryRestConfigurer which include cors parameter.

public class RestConfiguration implements RepositoryRestConfigurer {

    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
        config.setDefaultMediaType(MediaType.APPLICATION_JSON);
        config.useHalAsDefaultJsonMediaType(false);
    }
}

Sergio Gonzalez
  • 215
  • 1
  • 4
  • 11