Thursday, 18 April 2013

Spring MVC, Jackson and Filtering JSON Serialization



The default Spring JSON configuration will show all the fields of an object during serialization. Often we would like to return a subset of an entity’s properties and hide some which are only used internally such as the id and version.  Or perhaps return different properties depending on who/what is requesting the data.

As Spring uses Jackson for JSON serialization it supports the Jackson Json annotations. Jackson provides a number of options for “narrowing” or “filtering” the properties of an entity that gets serialized.

Jackson JsonViews (requires adding @JsonView to entity classes which pollutes them)
Jackson mix-in (requires extending the entity to specify @JsonIgnoreProperties)

Jackson Filtering options

Example of using Jackson views and mixin for filtering:

This one creates a new annotation to use with JsonViews but is a little too involved for my liking as it requires changing some Spring classes.

This link provides a good demo of Jackson Views and Mixins

Within my SearchController I was returning a SearchSummary object that referenced a Collector object. However, I didn’t want to return all the Collector details within the SearchSummary as its not relevant. Shown below is all I required:

@RequestMapping("/rest/loyalty")
@Controller
public class SearchController {

    @JsonIgnoreProperties({"loyaltyId","earnings","id","version"})
    private static class CollectorIdOnlyView extends Collector {
        // Empty by design ...
    } 
      
    private final ObjectMapper objectMapper = new ObjectMapper();

    private final MappingJacksonJsonView view = new MappingJacksonJsonView();      
    
    public SearchController() {         
        objectMapper.getSerializationConfig().addMixInAnnotations(Collector.class, CollectorIdOnlyView.class);
        view.setObjectMapper(objectMapper);
          
    }
    @Autowired
    private AccountService accountService;
      
    @Autowired
    private SearchService searchService;
      
    @RequestMapping(value = "/search/{id}", method = RequestMethod.GET)
    public View getSummary(@PathVariable("id") String id, Model uiModel) {
        Collector collector = accountService.findCollectorById(id);
        if(collector == null) {
              throw new RuntimeException("Collector Not Found");
        } else {
              SearchSummary searchSummary = searchService.getSearchSummary(collector);
              uiModel.addAttribute("summary",searchSummary);
              return view;
        }
       
    }
}

For the filtering I had to create a subclass of my entity to define the properties I wanted to ignore. (CustomerIdOnlyView).
This is then used to configure the ObjectMapper and View.
The change I had to make to the getSummary method was to change the return type from SearchSummary to View, add the SearchSummary to the Model and remove the @ResponseBody annotation.