Search code examples
javaspringspring-mvcspring-hateoashateoas

Spring Hateoas issues rendering HAL content


I've been checking similar issues and haven't found any answer addressing what I am observing.

The issue is that I've easily managed to get Hypermedia in HAL format in my REST API when I retrieve 1 resource, but when I hit the controller methods retrieving a list of entities, then the hypermedia is NOT the same.

Here are the ouputs:

  • single resource returned

    "_links": { "self": { "href": "http://localhost:8080/celsvs/api/books/123567891099" }, "books": { "href": "http://localhost:8080/celsvs/api/books" } }

  • List of resources

    "links": [ { "rel": "self", "href": "http://localhost:8080/celsvs/api/books/123567891099" }, { "rel": "books", "href": "http://localhost:8080/celsvs/api/books" } ]

I started with Spring hateoas 0.25, but as I had to uplift anyway Spring boot and I saw that the Hateoas API had changed, I am now on Spring hateoas 1.0... And even after adapting my code to the new API I am still getting the same result.

I am using the RepresentationModelAssemblerSupport class to keep my controllers clean from code to generate hateoas content. So this is how it looks like:

@Component
public class BookModelAssembler extends RepresentationModelAssemblerSupport<BookDto, BookDto> {

    public BookModelAssembler() {
        super(BooksController.class, BookDto.class);
    }

    @Override
    public BookDto toModel(BookDto entity) {
        return entity.add(linkTo(methodOn(BooksController.class).getResource(entity.getIsbn())).withSelfRel())
                     .add(linkTo(methodOn(BooksController.class).getAllResources()).withRel("books"));
    }
    
}

And in the Controller, the endpoints to retrieve one or all resources:

@Override
@GetMapping(value = {QueryConstants.BOOKS_ISBN_PATH, QueryConstants.BOOKS_SIG_PATH})
public BookDto getResource(@PathVariable("isbn") final String isbn) {
    
    return this.modelAssembler.toModel(this.findOneResource(isbn));
}


// GET - retrieve all resources

@Override
@GetMapping (produces = { "application/hal+json" })
public List<BookDto> getAllResources() {
    
    return (findAllResources().stream().map(this.modelAssembler::toModel).collect(Collectors.toList()));
}

As you can see, the Hypermedia rendered is different even when all the entities in the list returned have been mapped using the same method toModel() used in the method getResource().

The only way I've managed to see in the case of all resources the proper HAL format returned is when I've changed the implementation of the controller to return a collection Model:

//@GetMapping
public CollectionModel<BookDto> getAll() {
        return this.modelAssembler.toCollectionModel(findAllResources());
}

but then all the entities are bundled inside an _embedded element, which is NOT what I want when I return the collection of entities.

Spring Hateoas documentation states that HAL is the default, so I have not thought about configuring anything for now...

So, I only see:

  • I am missing some configuration so that when I can get a collection of entities rendered (no under the _embedded element)... But I haven't seen anything suitable in HalConfiguration bean.
  • I am assuming that the proper way of returning the collection of the type of resources requested is NOT inside the _embedded property... but maybe I am wrong. So far my understanding was that when you request, say, resource Person and you want to return it's contacts, those being also directly reachable via their own endpoint, then you embed those in the content returned along with the rest of Person properties... I haven't found anything stating that collections are expected to be rendered inside the _embedded property.

Does anyone have any advice? I am running out of time and ideas and the team implementing the client side is waiting to consume the Hypermedia content. Thanks!


Solution

  • The HAL specification says that the property _embedded is used to store an array of resource object.

    Edit: To answer Alberto's question in his own answer

    Still, if someone can tell me why in the previous implementation the attached links did not follow the HAL format, I would appreciate. Thanks

    Spring HATEOAS customizes JSON serialization of RepresentationModel which is the parent class of CollectionModel.

    // org.springframework.hateoas.mediatype.hal.RepresentationModelMixin
    public abstract class RepresentationModelMixin extends RepresentationModel<RepresentationModelMixin> {
    
        @Override
        @JsonProperty("_links")
        @JsonInclude(Include.NON_EMPTY)
        @JsonSerialize(using = Jackson2HalModule.HalLinkListSerializer.class)
        @JsonDeserialize(using = Jackson2HalModule.HalLinkListDeserializer.class)
        public abstract Links getLinks();
    }
    

    @JsonProperty("_links") defines the JSON property name to be _links. @JsonSerialize defines the serializer to be used. Look into method org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize for serialization logic.