Skip to content

When using toResources(), "links" is not correctly marshalled #493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
fdw opened this issue Sep 21, 2016 · 22 comments
Closed

When using toResources(), "links" is not correctly marshalled #493

fdw opened this issue Sep 21, 2016 · 22 comments
Assignees

Comments

@fdw
Copy link

fdw commented Sep 21, 2016

I'm using a method in a controller that returns a list of resources, built using toResources(). However, in the resulting json, the links are in a field called "links", and not "_links" as expected.

Example:

Code

public class ExampleResource extends ResourceSupport {
}
public class ExampleResourceAssembler extends ResourceAssemblerSupport<ExampleDao, ExampeResource> {
    public ExampleResource toResource(ExampleDao exampleDao) {
        // build resource and add link
    }
}
public List<ExampleResource> getProductInformation() {
    return exampleResourceAssembler.toResources(exampleDao);
}

Result:

[
  {
  "links": [
      {
        "rel": "self",
        "href": "http://localhost/example"
      }
    ]
  }
]
@odrotbohm
Copy link
Member

A ResourceAssembler assembles resources, i.e. it's responsibility is to collect the model (links + payload). It does not determine which media type that resource will be eventually rendered as. That's the responsibility of the marshaling infrastructure (usually an HttpMessageConverter using Jackson and some integration per media type).

@fdw
Copy link
Author

fdw commented Sep 21, 2016

Sorry, I forgot: The controller is annotated with @RequestMapping(value = "/products", produces = "application/hal+json;charset=utf-8")

The same setup works with a single resource (i.e. not a list).

Do you need any other information?

@odrotbohm
Copy link
Member

Are you sure the HAL support is actually activated using @EnableHypermediaSupport?

@fdw
Copy link
Author

fdw commented Sep 21, 2016

Yes, it is. As I said, the same setup works if I'm return just one resource instead of a list.

Oh, and we use spring-mvc-hateoas in version 0.21.0.RELEASE.

@DanailMinchev
Copy link

I have the same issue using default Spring Boot 1.4.1

As described here: http://docs.spring.io/spring-boot/docs/1.4.1.RELEASE/reference/htmlsingle/#boot-features-spring-hateoas:
I don't have @EnableHypermediaSupport, because I want Spring Boot's default configuration of HATEOAS.

So:

  • when I request one ResourceSupport object with application/json (or .json extension) the response is entity with _links property and the same with HAL / application/hal+json
  • when I request collection of List<UserResource> with application/json (or .json extension) the response is list of entities with links property and the same with HAL / application/hal+json

Looks like the media type is ignored and it is always _links for one resource and links for multiple resources.

@otrosien
Copy link

otrosien commented Dec 9, 2016

My feeling is, you're using the wrong (non-hal configured) jackson instance in your cases. It would be good to have a full example project on github to give you more detailed feedback.

@fdw
Copy link
Author

fdw commented Dec 14, 2016

Unfortunately, it would be quite complicated to post an example project, since we would need to strip out all proprietary code.

However, I can paste the relevant config snippets. The Controller, Resource and ResourceAssembler are as above.

@Configuration
@ComponentScan("com.example.rest")
@EnableHypermediaSupport(type = HAL)
@EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {

  @Autowired private BeanFactory beanFactory;

  @Bean
  public ObjectMapper jacksonMapper() {
    ObjectMapper halObjectMapper = (ObjectMapper) beanFactory.getBean("_halObjectMapper");

    halObjectMapper.configure(WRITE_DATES_AS_TIMESTAMPS, false);
    halObjectMapper.registerModule(new AfterburnerModule());
    halObjectMapper.registerModule(new JodaModule());

    return halObjectMapper;
  }

  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
  }

  @Override
  public void configureContentNegotiation(ContentNegotiationConfigurer c) {
    c.defaultContentType(MediaType.parseMediaType("application/hal+json"));
  }

@jochenchrist
Copy link

jochenchrist commented Feb 1, 2017

You could probably workaround wrapping your collection in Resources.

Instead of:

public List<ExampleResource> getProductInformation() {
    return exampleResourceAssembler.toResources(exampleDao);
}

return Resources

public Resources<ExampleResource> getProductInformation() {
    return exampleResourceAssembler.toResources(exampleDao);
}

However the rendering changes, as the list elements are embedded in an _embedded field.

@gregturn
Copy link
Contributor

Is this related to #470, a PR that moved at add a UTF-8 based MediaType to compensate for Spring Boot moving to APPLICATION_JSON_UTF8 as the default "produces" for Spring MVC REST response types?

@fdw
Copy link
Author

fdw commented Mar 26, 2017

Hi @gregturn, I don't think so, no. Our problem highlights a different marshalling when using a List<? extends ResourceSupport instead of ResourceSupport. The first is wrong while the second works correctly, both using the same MediaType in the tests.

@abobwhite
Copy link

I am seeing this too. Been struggling with it for a couple weeks and can't find the reason.

@gregturn
Copy link
Contributor

Pretty sure you need to use Resources to get the collection to render properly.

It's evident in the posted JSON where the top of the structure is an array not a map. Returning List<Resource> As your top level structure is not a valid HAL structure hence bypassing the HAL serializers.

@fdw
Copy link
Author

fdw commented Sep 18, 2017

But if using a List<? extends ResourceSupport> is wrong, why is there a toResources() method in ResourceAssemblerSupport? This is not very intuitive...

@gregturn
Copy link
Contributor

ResourceAssemblerSupport has some problems. That's why I wrote #591.

In its current form, you should return a Resources with the contents of toResources inside it.

@gregturn
Copy link
Contributor

Related to #416

@abobwhite
Copy link

@gregturn Makes sense, however, the ResourceAssembler toResources returns a List, not Resources....how would we go about managing that variance? Resources.wrap? Is there any preferred approach?

@gregturn
Copy link
Contributor

If you look at #416, you'll notice the updated API stuffs that inside a Resources object. While you can't use the new code, you can certainly emulate it by inserting toResources into a new Resources object, and having your controller return that. That should certainly yield a different output.

@abobwhite
Copy link

@gregturn That indeed works and so does Resources.wrap....however, now everything is nested under a root _embedded object.....the fun continues.

@gregturn
Copy link
Contributor

And when serving multiple resources in HAL, this is the proper format. non-_embedded is only for single items.

@odrotbohm
Copy link
Member

But if using a List<? extends ResourceSupport> is wrong, why is there a toResources() method in ResourceAssemblerSupport? This is not very intuitive...

Hm, not sure I can follow. toResources() takes a list of elements and transforms each of them into Resource instances. I.e. you wrap each element to create hypermedia-enabled variants of those elements and a List of those. Whether you want to enable hypermedia aspects for the entire list, is a completely orthogonal decision. So the pattern we usually see is something like:

return new Resources<>(assembler.toResources(sourceList));

which makes both steps pretty obvious. I'd argue that's SRP principle in practice.

Maybe we can add a variant of that method (….map(…)?) that returns a builder which has to be concluded with either toList() or toResources()

gregturn added a commit that referenced this issue Sep 26, 2017
To avoid confusion about what to return on a Spring MVC endpoint, change toResources to not return `List<D>`, but instead `Resources<D>`. This clearly ensures when used to construct Spring MVC endpoints, will return a type Spring HATEOAS will properly marshal.

Related issues: #493
Related pull requests: #572
gregturn added a commit that referenced this issue Sep 26, 2017
To avoid confusion about what to return on a Spring MVC endpoint, change toResources to not return `List<D>`, but instead `Resources<D>`. This clearly ensures when used to construct Spring MVC endpoints, will return a type Spring HATEOAS will properly marshal.

Related issues: #493
Related pull requests: #572
gregturn added a commit that referenced this issue Dec 6, 2018
To avoid confusion about what to return on a Spring MVC endpoint, change toResources to not return `List<D>`, but instead `Resources<D>`. This clearly ensures when used to construct Spring MVC endpoints, will return a type Spring HATEOAS will properly marshal.

Related issues: #493
Related pull requests: #572
gregturn added a commit that referenced this issue Dec 6, 2018
To avoid confusion about what to return on a Spring MVC endpoint, change toResources to not return `List<D>`, but instead `Resources<D>`. This clearly ensures when used to construct Spring MVC endpoints, will return a type Spring HATEOAS will properly marshal.

Related issues: #493
Related pull requests: #572
@gregturn
Copy link
Contributor

gregturn commented Dec 6, 2018

Hm, not sure I can follow. toResources() takes a list of elements and transforms each of them into Resource instances. I.e. you wrap each element to create hypermedia-enabled variants of those elements and a List of those.

This would be clearer if we didn't actually have a Resources type. Since we do, I think too many people assume that's what you get.

Additionally, people probably don't realize that Spring HATEOAS's hypermedia serialization doesn't kick in if a Spring MVC route don't return ResourceSupport or one of its subtypes. It's why I keep seeing issues opened showing.

To support things, I'm adjusting #632 to have both. Or if they are simply returning that output on a Spring MVC route, they can adjust suitably.

gregturn added a commit that referenced this issue Dec 6, 2018
To avoid confusion about what to return on a Spring MVC endpoint, change toResources to not return `List<D>`, but instead `Resources<D>`. This clearly ensures when used to construct Spring MVC endpoints, will return a type Spring HATEOAS will properly marshal.

To support people that have already built apps on top of Lists, include a `toList<D>` method that honors the old contract.

Related issues: #493
Related pull requests: #572

Hack
gregturn added a commit that referenced this issue Dec 18, 2018
To avoid confusion about what to return on a Spring MVC endpoint, change toResources to not return `List<D>`, but instead `Resources<D>`. This clearly ensures when used to construct Spring MVC endpoints, will return a type Spring HATEOAS will properly marshal.

To support people that have already built apps on top of Lists, include a `toList<D>` method that honors the old contract.

Related issues: #493
Related pull requests: #572
@gregturn gregturn assigned gregturn and unassigned odrotbohm Jan 15, 2019
@gregturn
Copy link
Contributor

The return type needs to be either ResourceSupport or one of its subtypes. The resource assemblers presently used now return these via #416 and #572. Update your Spring MVC endpoints return types and the JSON should render properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants