Gaurav Rawat
Gaurav Rawat

Reputation: 1324

Filtering entity fields dynamically in Spring Data rest json Response

Hi I have a requirement to dynamically ignore entity fields in spring data rest response [I know they can be done in a static way by using @JsonIgnore annotation] ideally based on a spring security Role .The role part is still manageable but how to dynamically ignore fields in the json response is a challenge. After some analysis and the docs I think jackson is the way to go as spring data rest does provide jackson customization via jackson modules and mixins http://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.custom-jackson-deserialization .

So I think in jackson api it could be done via @jsonFilter and then suppling the same when the ObjectMapper write the object [more details here http://www.cowtowncoder.com/blog/archives/2011/09/entry_461.html] .

But I am not sure how this could be wired up with Spring data rest (basically the part where I acan inject the filterprovider into spring data rest objectmapper).Let me know if anyone has tried this or someone from the Spring data team has insights .

Will post an answer myself If I am able to achieve the same.

UPDATE

So I figured out that the way to implement custom filtering is through the jackson BeanSerializerModifier .Got great help from @cowtowncoder on twitter .Also helpful reference or holy grails for filtering with jackson http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html

Upvotes: 4

Views: 5400

Answers (2)

Andrey Grigoriev
Andrey Grigoriev

Reputation: 438

This example shows how to implement a dynamic JSON transformation (filtering) in a Spring Boot REST controller. It is using AOP controller advice to change controller method output in runtime. Code on github: https://github.com/andreygrigoriev/jsonfilter

AOP Advice

@ControllerAdvice
@SuppressWarnings("unused")
public class FilterAdvice implements ResponseBodyAdvice<Object> {

   @Override
   public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
      String fields = ((ServletServerHttpRequest) request).getServletRequest().getParameter("fields");
      return new FilterMappingJacksonValue<>(body, StringUtils.isEmpty(fields) ? new String[] {} : fields.split(","));
   }

   @Override
   public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
      return true;
   }
}

FilterMappingJacksonValue

public class FilterMappingJacksonValue<T> extends MappingJacksonValue {

   public FilterMappingJacksonValue(final T value, final String... filters) {
      super(value);
      setFilters(new SimpleFilterProvider().addFilter("dynamicFilter",
            filters.length > 0 ? SimpleBeanPropertyFilter.filterOutAllExcept(filters) : SimpleBeanPropertyFilter.serializeAll()));
   }
}

Simple DTO

@Data
@AllArgsConstructor
@JsonFilter("dynamicFilter")
public class Book {
   String name;
   String author;
}

BookController

@RestController
@SuppressWarnings("unused")
public class BookController {

   @GetMapping("/books")
   public List<Book> books() {
      List<Book> books = new ArrayList<>();
      books.add(new Book("Don Quixote", "Miguel de Cervantes"));
      books.add(new Book("One Hundred Years of Solitude", "Gabriel Garcia Marquez"));
      return books;
   }
}

Upvotes: 1

Gaurav Rawat
Gaurav Rawat

Reputation: 1324

So yes finally I was able to solve this .The trick here is to use a custom BeanSerializerModifier and register it via a Custom Module [which is the custom hook available to customize spring data rest jackson serialization],something like

 setSerializerModifier( new CustomSerializerModifier()).build()));

now you can customize our BeanSerializerModifier by overriding the method changeProperties to apply your custom filter ,which basically includes and excludes BeanPropertyWriter based on your logic .sample below

List<BeanPropertyWriter> included = Lists.newArrayList();
    for (BeanPropertyWriter property : beanProperties)
        if (!filter.contains(property.getName()))
            included.add(property);

this way you can include any logic per class or otherwise and filter properties form response in a custom manner.Hope It Helps

Also have updated my code on github do look at https://github.com/gauravbrills/SpringPlayground

Upvotes: 5

Related Questions