David Harrigan opened SPR-12552 and commented
Hi,
Spring 4.1.3 Jackson 2.4.4
I have a class that looks something like this:
public class Foo {
public interface Summary{}
@JsonView(Foo.Summary.class)
private String name;
private String password;
}
and a controller that looks something like this:
@JsonView(Foo.Summary.class)
public ResponseEntity<PagedResources<Resource<Foo>>> getFoos() {
final Page<Foo> foos = serviceLayer.getFoos();
return new ResponseEntity<>(pagedResourcesAssember.toResource(foos), HttpStatus.OK);
}
However, when I get the values returned, they include everything:
{
"links": {
.....
.....
},
"content" : [
{
"name": "billy",
"password": "sssh"
},
{
"name": "goat",
"password": "drowssap"
}
],
"pages": {
....
....
}
}
As you can see, the @JsonView
is not being honoured during the serialization of the Content data as the "password" field is being included in the serialization.
Thank you.
-=david=-
Affects: 4.1.3
Comment From: spring-projects-issues
Sébastien Deleuze commented
Hi David,
I did some tests and found that using @EnableHypermediaSupport
replace the ObjectMapper
instance used with another one not created by Jackson2ObjectMapperBuilder
. One of the consequences is that the MapperFeature.DEFAULT_VIEW_INCLUSION
feature is enabled when using Spring HATEOAS instead of disabled like in Spring MVC or Spring Boot default configuration. Since password
is not annotated, it is included in the output.
As a workaround, you can create an Jackson2ObjectMapperFactoryBean
(XML) or an ObjectMapper
bean created with Jackson2ObjectMapperBuilder
(JavaConfig) using the _halObjectMapper
name, I think it will be detected and used by Spring HATEAOS.
Even if Spring HATEOAS still depends on Spring 3.X by default, I think it could be fixed by using a Jackson2ObjectMapperFactoryBean
bean instead of a raw ObjectMapper
instance, like what I did in Spring MVC. Since Jackson2ObjectMapperFactoryBean
automatically uses the improved default configuration in latest Spring versions, it is likely to fix your issue.
Could you please create an issue on Spring HATEOAS bug tracker with a link to this one ?
Comment From: spring-projects-issues
David Harrigan commented
Hi,
Perhaps I'm misunderstanding..but...
I actually do inject my own ObjectMapper into the configuration and I can confirm that it is used. In AbstractJackson2HttpMessageConverter (4.1.3 Release), lines 222-229 are being invoked, i.e., serializationView != null is true, and the objectMapper.writerWithView is being invoked (with the Summary view being the View that has been choosen).
Are you saying that this is then ignored by Hateos?
I am not using EnableHypermediaSupport as well, as if you are referring to Lines 624-638 (defaultMethodArgumentResolvers) and lines 642-655 (basicObjectMapper) of RespositoryRestMvcConfiguration (spring-data-rest-mvc (2.3.0.M1)). These are not being used (no breakpoint was activated on application start, or message serialization). In "defaultMessageConverters" in RespositoryRestMvcConfiguration, none of the messageConverters.add are being used. So, in reality, I'm not using the EnableHypermediaSupport as far as I can tell.
For example, here is where I inject the ObjectMapper into the MappingJackson2HttpMessageConverter.
<mvc:annotation-driven validator="smartValidator" content-negotiation-manager="contentNegotiatingManager">
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="enhancedJackson2ObjectMapper"/>
</bean>
</mvc:message-converters>
-=david=-
Comment From: spring-projects-issues
Sébastien Deleuze commented
If you are using your own ObjectMapper
, could you verify that MapperFeature.DEFAULT_VIEW_INCLUSION
is set to false
(by default it is set to true so you have to disable it explicitly) ?
When MapperFeature.DEFAULT_VIEW_INCLUSION
is set to true, non annotated properties are serialized, I guess that's why you have the password property serialized.
Comment From: spring-projects-issues
David Harrigan commented
Hi,
Yes, I'm using it, and it is set to false.
However, I believe I've also discovered the cause of the Issue.
On org.springframework.hateoas.Resource, getContent (line 74), is marked as @JsonUnwrapped
. This causes the JsonView not to work.
Proof:
This works:
@JsonView(Foo.Summary.class)
@RequestMapping(value = "/foo", method = GET, produces = JSON)
public Callable<ResponseEntity<Foo>> getFoo(final PagedResourcesAssembler<Foo> pagedResourcesAssembler) {
return () -> {
final Foo foo = new Foo();
foo.setName("Billy");
foo.setPassword("Password");
return new ResponseEntity<>(foo, HttpStatus.OK);
};
}
Result:
curl http://localhost:8080/foo
{ "name": "Billy"}
This does not:
@JsonView(Foo.Summary.class)
@RequestMapping(value = "/foo", method = GET, produces = JSON)
public Callable<ResponseEntity<Resource<Foo>>> getFoo(final PagedResourcesAssembler<Foo> pagedResourcesAssembler) {
return () -> {
final Foo foo = new Foo();
foo.setName("Billy");
foo.setPassword("Password");
Resource<Foo> fooResource = new Resource<>(foo);
return new ResponseEntity<>(fooResource, HttpStatus.OK);
};
}
Result:
curl http://localhost:8080/foo
{}
It appears that using the JsonUnwrapped attribute, causes the View not to work as expected. Do you think the issue now is with Jackson or with Hateos Resource?
Comment From: spring-projects-issues
Sébastien Deleuze commented
I think this is the expected behavior: with MapperFeature.DEFAULT_VIEW_INCLUSION
set to false, properties without @JsonView
annotation, like Resource
content
and links
ones, are not serialized. That's probably why you have an empty object in your second example.
In your initial comment, all the data was serialized, that why I thought you had MapperFeature.DEFAULT_VIEW_INCLUSION
set to true.
My proposal to make that works would be to configure MapperFeature.DEFAULT_VIEW_INCLUSION
to true, and annotates all the fields with multiple views, for example:
@JsonView(Foo.Summary.class)
private String name;
@JsonView(Foo.Full.class)
private String password;
Comment From: spring-projects-issues
David Harrigan commented
Hi,
That doesn't appear to work. Setting DEFAULT_VIEW_INCLUSION = true, and having a "Summary" view on a property and a "Full" view on another property, then having the "Summary" view on the controller, just causes all the properties to be serialised (in effect, the "Summary" view does not get applied).
If I set the DEFAULT_VIEW_INCLUSION = false, then nothing comes out.
Comment From: spring-projects-issues
Sébastien Deleuze commented
Could you have a look to this sample project I have created ?
When I request http://localhost:8080/resource
, I have the Summary
view working as expected using a ResponseEntity<Resource<Message>>
return value.
Please provide a reproduction project if that does not help.
Comment From: spring-projects-issues
Sébastien Deleuze commented
Hi David Harrigan, could send me your feedback ?
Comment From: spring-projects-issues
David Harrigan commented
Hi!
I've not had a chance to try and reproduce this issue yet. I've also moved on from it a bit, since I've found that using this https://jira.spring.io/browse/DATACMNS-618 gives me more flexibility and choice on which data to return depending on a parameter passed in. I'm happy enough for you to close this JIRA if you believe it appropriate.
Thank you.
-=david=-
Comment From: spring-projects-issues
Sébastien Deleuze commented
OK thanks. Since the sample project I have created seems to demonstrate the expected behavior, I resolve this issue as "Cannot Reproduce" until someone fork and modify it to reproduce the issue.
Comment From: spring-projects-issues
David Harrigan commented
Hi,
Okaydokey :-) Thank you for your care and attention to the issue :-)
-=david=-
Comment From: spring-projects-issues
Jonathan Rodrigues de Oliveira commented
Hey guys. Think I have something close to this. I want to use JsonView on a method that returns a page of POJOS. See the code:
@RestController
public class ImovelRestController {
//stuff
@RequestMapping(value = "/imoveis/a", method = GET)
@JsonView(ListagemImovel.class)
Iterable<Imovel> a() {
return imovelRepository.findAll();
}
@RequestMapping(value = "/imoveis/b", method = GET)
@JsonView(ListagemImovel.class)
Page<Imovel> b() {
return imovelRepository.findAll(new PageRequest(0, 20, sort));
}
The method "a" works ok. The method "b" returns and empty object "{}". Is that some issue with JsonView and Page or am I doing something wrong?
Thanks.
Comment From: spring-projects-issues
Sébastien Deleuze commented
Hi, did you set MapperFeature.DEFAULT_VIEW_INCLUSION
to true (default is false) and eventually add missing @JsonView
annotation (see http://wiki.fasterxml.com/JacksonJsonViews for more details) according to previous comments?
Comment From: spring-projects-issues
Jonathan Rodrigues de Oliveira commented
Hey. If I set default view inclusion to true, the method "b" returns the full objects, with all fields.
The config and the entity are like this:
@Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true);
builder.dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
builder.defaultViewInclusion(true);
return builder;
}
@Entity
public class Imovel extends EntidadeAuditavel implements Serializable, Comparable<Imovel> {
@Column(length = 30)
@JsonView(JsonViews.ListagemImovel.class)
private String codigo = "";
@NotBlank
@JsonView(JsonViews.ListagemImovel.class)
private String nome = "";
//stuff
I tried to annotate the getters with @JsonView
, but that doesn't work either...
Comment From: spring-projects-issues
Jonathan Rodrigues de Oliveira commented
Hi Sébastien.
I've made some tests, and @JsonView
does not work when my controller method returns Page\
Thanks.
Comment From: spring-projects-issues
Madanraj Sadasivam commented
Hi Guys,
I am having the same issue. RestController returns empty json if annotated with @JsonView
and returns org.springframework.hateoas.Resource. However the @JsonView
works if the RestController returns just the DTO without the hateoas resource.
@Sebastien
,
I can send you further details of my code, and spring/jackson version, if it will help. Please let me know.
I also think we should reopen the ticket.
Comment From: spring-projects-issues
Sébastien Deleuze commented
Yes please send me your sample project, I will have a look.
Comment From: spring-projects-issues
Madanraj Sadasivam commented
Hi Sebastien,
The sample project is below. Try running on a web server and access the url "http://localhost:8080/SpringSampleProject/user" with both GET and POST to see the difference.
RestController is "com.aakam.sample.rest.UserController"
GitHub Repo https://github.com/madanrajs/SpringSampleProject
Comment From: spring-projects-issues
Sébastien Deleuze commented
As explained in previous comments, when using classes with non @JsonView
annotated fields, you need to set Jackson defaultViewInclusion
to true
, and modify your DTO annotations accordingly. I have created a pull request for your sample project with the relevant changes.
Another (untested) option may be to use Jackson Mixins to specify that the Resource#content
property should be included in your JSON view.
Could you confirm that it works as expected?
Comment From: spring-projects-issues
Madanraj Sadasivam commented
Yes, it works. Thanks.
Comment From: mupezzuol
Someone know how to use @Valid + @JsonView together? @JsonView isn't working using @Valid. :(