Skip to content

@JsonView not working with Resources [SPR-12552] #17154

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
spring-projects-issues opened this issue Dec 18, 2014 · 21 comments
Closed

@JsonView not working with Resources [SPR-12552] #17154

spring-projects-issues opened this issue Dec 18, 2014 · 21 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: declined A suggestion or change that we don't feel we should currently apply

Comments

@spring-projects-issues
Copy link
Collaborator

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

@spring-projects-issues
Copy link
Collaborator Author

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 ?

@spring-projects-issues
Copy link
Collaborator Author

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=-

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

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?

@spring-projects-issues
Copy link
Collaborator Author

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;

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

Hi David Harrigan, could send me your feedback ?

@spring-projects-issues
Copy link
Collaborator Author

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=-

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

David Harrigan commented

Hi,

Okaydokey :-) Thank you for your care and attention to the issue :-)

-=david=-

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

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?

@spring-projects-issues
Copy link
Collaborator Author

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

@spring-projects-issues
Copy link
Collaborator Author

Jonathan Rodrigues de Oliveira commented

Hi Sébastien.

I've made some tests, and @JsonView does not work when my controller method returns Page<MyClass> or PagedResource<MyClass>.
Indeed it works when List<MyClass> is returned.
I know that this issue is closed. Do you have some idea on this? Should I open a new issue?

Thanks.

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

Yes please send me your sample project, I will have a look.

@spring-projects-issues
Copy link
Collaborator Author

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

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

Hi Madanraj Sadasivam,

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?

@spring-projects-issues
Copy link
Collaborator Author

Madanraj Sadasivam commented

Yes, it works. Thanks.

@spring-projects-issues spring-projects-issues added type: bug A general bug status: declined A suggestion or change that we don't feel we should currently apply in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues removed the type: bug A general bug label Jan 12, 2019
@mupezzuol
Copy link

Someone know how to use @Valid + @JSONVIEW together? @JSONVIEW isn't working using @Valid. :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

3 participants