Skip to content
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

Support Jackson @JsonFilter [SPR-12586] #17187

Closed
spring-issuemaster opened this issue Dec 31, 2014 · 18 comments

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

commented Dec 31, 2014

Christopher Shannon opened SPR-12586 and commented

I occasionally need to be able to dynamically filter what fields are returned from a rest controller. The Jackson @JsonView annotation is a good improvement but it still requires configuration ahead of time and isn't dynamic.

One use case I have is that a user will pass in a list of parameters specifying what they want to see (or exclude) in a result set. Based on that list the result set could then be customized for the user. A way this can be done with Jackson is using filters.

Here is an example:

@JsonFilter("someFilter")
class MyBean {
    ...
}

MyBean value;
//business logic to set value
ObjectMapper mapper = new ObjectMapper();
FilterProvider filters = new SimpleFilterProvider().addFilter("someFilter",
    SimpleBeanPropertyFilter.filterOutAllExcept("someProperty"));

String json = mapper.filteredWriter(filters).writeValueAsString(value);
//....

It would be nice to be able to do this with Spring MVC using either the MappingJackson2HttpMessageConverter or a view (MappingJackson2JsonView)

For controllers using view resolution I think it would be pretty straight forward. The filter could be applied the same way a JsonView is done now, it could be done like this:

    @RequestMapping(value = "/data", method = RequestMethod.GET)
    public String getData(Model model) {
        //business logic to create or look up filter
        FilterProvider filters = new SimpleFilterProvider().addFilter("someFilter",
            SimpleBeanPropertyFilter.filterOutAllExcept("someProperty"));

        //add to model
        model.addAttribute('jsonFilters', filters);

        return "dataView";
    }

And then the MappingJackson2JsonView class could read that value to configure the serializer.

It gets a little more tricky with the message converter set up (which is what I mosly use) since the AbstractJackson2HttpMessageConverter needs a way to dynamically know what filter to use. Maybe the controller method could return a wrapper type that could be detected by the message converter. Something like:

    @RequestMapping(value = "/data", method = RequestMethod.GET)
    @ResponseBody
    public Object getData() {
        MyObject data;
        //....do some business logic and set data
        //create or look up filter
        FilterProvider filters = new SimpleFilterProvider().addFilter("someFilter",
            SimpleBeanPropertyFilter.filterOutAllExcept("someProperty"));

        return new FilterWrapper(filters, data);
    }

If the converter detects that FilterWrapper type then it could set up the writer to use the filter when serializing. The filter would need to be configured dynamically at runtime so an annotation on the controller method wouldn't work in this case.

Let me know what you think.


Affects: 4.1.4

Reference URL: http://wiki.fasterxml.com/JacksonFeatureJsonFilter

Issue Links:

  • #19730 Support @JsonFilter on @ResponseBody and ResponseEntity Controller methods ("is duplicated by")
  • #21742 Support both filters and views in AbstractJackson2HttpMessageConverter

Referenced from: commits ca06582

2 votes, 14 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 5, 2015

Sébastien Deleuze commented

I think that could be a nice addition to our current @JsonView support.

@JsonFilter provides by default a filtering mechanism close to Spring Data REST projections, but can also be used to implement more flexible mechanisms based on custom FilterProvider and BeanPropertyFilter. This annotations can be used on methods and parameters, so we will be able to use it like we use @JsonView.

Let's tentatively target 4.2 RC1 for this one.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 5, 2015

Christopher Shannon commented

Great, thanks for considering adding this feature in 4.2 as that will make it even easier to generate dynamic content for a user.

Having @JsonFilter be able to be added to methods/parameters would be good. But what about when an argument (such as a list of fields to include/exclude) needs to be passed to a custom FilterProvider or BeanPropertyFilter like in my example above?. I mentioned maybe returning a wrapper object, but based on your comments are you thinking about doing something like being able to add @JsonFilter to a request parameter on the controller method? That could work as well depending on how flexible it was.

Thanks,
Chris

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Mar 31, 2015

Sébastien Deleuze commented

Hi Christopher Shannon, indeed that would be useful to support dynamic filtering as well. I guess we could implement that by adding a filterProvider property to MappingJacksonValue in order to support this use case, like we do currently for @JsonView support. See bellow some updated code sample of what it could look like:

@JsonFilter("someFilter")
class MyObject {
    ...
}

@Controller
public class MyController {
	
	@RequestMapping("/data")
	@ResponseBody
	public MyObject getData() {
		FilterProvider filterProvider = new SimpleFilterProvider().addFilter("someFilter",
			SimpleBeanPropertyFilter.filterOutAllExcept("someProperty"));
		MyObject data;
		//....do some business logic and set data
		MappingJacksonValue wrapper = new MappingJacksonValue(data);  	
		wrapper.setFilterProvider(filterProvider);
		return wrapper;
	}

	@RequestMapping("/data-view")
	public String getDataView(Model model) {
		FilterProvider filterProvider = new SimpleFilterProvider().addFilter("someFilter",
			SimpleBeanPropertyFilter.filterOutAllExcept("someProperty"));
		MyObject data;
		//....do some business logic and set data
		model.addAttribute("data", data);
		model.addAttribute(FilterProvider.class.getName(), filterProvider);
		return "dataView";
	}

}

Edit: I have removed the static code examples with handler method annotated with @JsonFilter since they do not make sense. Unlike what I thought initially, @JsonFilter does not behave like Spring Data projections. The POJO to serialize need to be annotated with @JsonFilter("filterName") and the filter provider defines programmatically what properties will be serialized.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 1, 2015

Christopher Shannon commented

That sample code looks good to me and should do exactly what I need. I'm looking forward to the 4.2 release candidate to test it out.

Thanks,
Chris

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 1, 2015

Sébastien Deleuze commented

The implementation of this improvement is available on this branch.

Christopher Shannon Could you have a look to my updated comment, and confirm that the static examples does not make sense for you as well?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 1, 2015

Christopher Shannon commented

Sébastien Deleuze, I was actually looking at this a bit today after I commented and I realized the same thing about the static examples. Your example still works for me because my use case is for dynamic content and I don't need the static examples anyways.

One thing that I noticed is that if you add a filter annotation using @JsonFilter on the POJO or by a mixin, an error will be thrown if you attempt to serialize that object with no filter registered. The error says something like "No filter configured with id..." This can be kind of a pain because there are many times when it is desirable to reuse the same POJO and to serialize it without a filter.

This ticket explains this issue better: https://jira.codehaus.org/browse/JACKSON-650

The way to fix this is to set either a default filter on SimpleFilterProvider or to set the following on the mapper mapper.setFilters(new SimpleFilterProvider().setFailOnUnknownId(false));

It is easy enough to set a default filter on SimpleFilterProvider but for the case where someone wants to set it on the mapper It might make sense to add a configuration option for this to Jackson2ObjectMapperBuilder or Jackson2ObjectMapperFactoryBean , if it's possible to do this.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 2, 2015

Sébastien Deleuze commented

Good idea, I have added such configuration option to Jackson2ObjectMapperBuilder or Jackson2ObjectMapperFactoryBean.

This improvement has just been commit in master, and will be available in our 4.2.0.BUILD-SNAPSHOT builds shortly. Feel free to test and send us your feedback.

Thanks.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 2, 2015

Christopher Shannon commented

Great, thanks Sébastien Deleuze. I'll take a look at the snapshot in the next few days and let you know how it goes.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 25, 2015

Christopher Shannon commented

Sébastien Deleuze, I finally got around to testing this out. My use case is primarily REST so I focused most of my testing on returning a MappingJacksonValue wrapper. I tried various scenarios and configuration options and it all worked as intended. Thanks again for adding this feature.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 27, 2015

Sébastien Deleuze commented

chris8204 Thanks for your feedback!

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 26, 2015

rk commented

How come this does not work for nested properties? I looked at the logic and it only checks fields on current object properties not nested objects. Am I missing something?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 2, 2015

Martin Frey commented

Nested properties filtering is just not possible by default by Jackson-databind. I have created a little addon library at the time to be able tu use the "antpath" approach for nested filtering.

https://github.com/Antibrumm/jackson-antpathfilter

I was pointed to this issue here from one issue on my github repo. I did some investigation and as a result i have updated the readme with an approach that allows using MappingJacksonValue. Hope this helps you.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 2, 2015

Sébastien Deleuze commented

Nice, thanks for sharing that!

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented May 9, 2016

Izek Greenfield commented

how this could work with DeferredResult

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented May 9, 2016

Izek Greenfield commented

When I use this with 'DeferredResult' like this:

innerResult: DeferredResult[Object]

override def setResult(result: T): Boolean = {
    val beanPropertyFilter: SimpleBeanPropertyFilter = filter.size match {
      case 0 => SimpleBeanPropertyFilter.serializeAll()
      case _ => SimpleBeanPropertyFilter.filterOutAllExcept(filter)
    }
    val filterProvider = new SimpleFilterProvider().addFilter("propertiesFilter", beanPropertyFilter)
    val wrapper = new MappingJacksonValue(result)
    wrapper.setFilters(filterProvider)

    innerResult.setResult(wrapper)
  }

the response looks:

{"headers":{},"body":[{"id":"573080B50CCDED33E08DA678"}],"statusCode":"OK"}

while I expect it to be only:

[{"id":"573080B50CCDED33E08DA678"}]

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented May 30, 2017

Markus Pscheidt commented

Martin Frey's Antpathfilter is a very useful addition to Spring's support for @JsonFilter. Nested properties plus no need to annotate beans with @JsonFilter.

Any chance to add Antpathfilter to Spring Framework?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented May 30, 2017

Sébastien Deleuze commented

We are quite late in Spring Framework 5.0 development cycle, but feel free to create an issue avbout that. That will allow us to evaluate how many people are interested and discuss about this feature.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented May 30, 2017

radha krishna commented

We ended up writing a small adapter that was shared with others on a stackoverflow request as these existing filters were not efficient for our case:
https://github.com/krishna81m/jackson-nested-prop-filter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.