@JsonView
annotation declared on a controller method. But I don't think Jackson's views are good for that. First of all you have to annotate methods of the class itself with @JsonView
to filter its properties out. Then you have to define additional classes for the views. I think it'd be much easier just to list all the required properties for a controller method itself. So I added custom annotations for Spring MVC controllers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | /** * Annotation applied to a Spring MVC {@code @RequestMapping} or {@code @ExceptionHandler} method to filter out * its response body during JSON serialization. * <p/> * Example: * <pre> * public class PropSet extends PredefinedPropSet { * public PropSet() { * super("propA"); * } * } * * @JsonFilter(target = Bean.class, include = {"propB"}, propSets = PropSet.class) * public @RequestBody getBean() { * ... * } * </pre> * * @author Oleg Galkin * @see com.fasterxml.jackson.databind.ObjectMapper#setFilterProvider(com.fasterxml.jackson.databind.ser.FilterProvider) * @see com.fasterxml.jackson.databind.ObjectMapper#setAnnotationIntrospector(com.fasterxml.jackson.databind.AnnotationIntrospector) */ @Documented @Target (METHOD) @Retention (RUNTIME) public @interface JsonFilter { /** * Class the filter is used for. */ Class<?> target(); /** * Explicitly defined properties that should be kept when serializing the specified class to JSON. */ String[] include() default {}; /** * {@link PredefinedPropSet Property sets} that define the properties included in JSON. * Each property set class must have a default constructor. */ Class<? extends PredefinedPropSet>[] propSets() default {}; } /** * Declares several {@link JsonFilter} annotations for different classes on the same method. * * @author Oleg Galkin */ @Documented @Target (METHOD) @Retention (RUNTIME) public @interface JsonFilters { /** * Defined filters. */ JsonFilter[] value() default {}; } |
1 2 3 4 5 6 7 8 9 | @JsonFilters ({ @JsonFilter (target = User. class , propSets = {IdPropSet. class , UserPropSet. class }), @JsonFilter (target = Role. class , include = { "id" , "name" }) }) @RequestMapping (value = "/user/{id}" , method = RequestMethod.GET) @ResponseBody public User getUser( long id) { ... } |
PredefinedPropSet
.To remove unnecessary properties we need Jackson's filter that only serializes the allowed object properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public class ExceptPropertyFilter implements PropertyFilter { private final Map<Class<?>, Set<String>> filters = Maps.newHashMap(); ... @Override public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider prov, PropertyWriter writer) throws Exception { if (include(pojo, writer)) { writer.serializeAsField(pojo, jgen, prov); } else if (!jgen.canOmitFields()) { writer.serializeAsOmittedField(pojo, jgen, prov); } } ... protected boolean include(Object object, PropertyWriter writer) { Validate.notNull(object); Set<String> properties = getProperties(object); return properties == null || properties.contains(writer.getName()); } private Set<String> getProperties(Object object) { for (Class<?> cls = object.getClass(); cls != Object. class ; cls = cls.getSuperclass()) { Set<String> fields = filters.get(cls); if (fields != null ) { return fields; } } return null ; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class JsonFilterAnnotationIntrospector extends JacksonAnnotationIntrospector { private final String filterId; ,,, @Override public Object findFilterId(Annotated ann) { Object id = super .findFilterId(ann); if (id == null ) { JavaType javaType = TypeFactory.defaultInstance().constructType(ann.getRawType()); if (!javaType.isContainerType()) { id = filterId; } } return id; } } |
MappingJackson2HttpMessageConverter
will use. It can be done by overriding a WebMvcConfigurerAdapter
method:
1 2 3 4 5 6 7 8 | @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() .annotationIntrospector( new JsonFilterAnnotationIntrospector()) .filters( new SimpleFilterProvider().setFailOnUnknownId( false )) .build(); converters.add( new MappingJackson2HttpMessageConverter(objectMapper)); } |
@JsonFilter
is invoked by implementing Spring MVC ResponseBodyAdvice
interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | /** * {@link ResponseBodyAdvice} implementation that supports the {@link JsonFilter} annotation declared on a Spring MVC * {@code @RequestMapping} or {@code @ExceptionHandler} method. * <p/> * The created {@link ExceptPropertyFilter} is used within {@link MappingJackson2HttpMessageConverter} to serialize * the response body to JSON. * * @author Oleg Galkin * @see JsonFilter * @see ExceptPropertyFilter * @see MappingJackson2HttpMessageConverter */ @ControllerAdvice public class JsonFilterResponseBodyAdvice extends AbstractMappingJacksonResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return super .supports(returnType, converterType) && (returnType.getMethodAnnotation(JsonFilter. class ) != null || returnType.getMethodAnnotation(JsonFilters. class ) != null ); } @Override protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) { ExceptPropertyFilter filter; JsonFilter annotation = returnType.getMethodAnnotation(JsonFilter. class ); if (annotation != null ) { filter = new ExceptPropertyFilter(annotation); } else { filter = new ExceptPropertyFilter(returnType.getMethodAnnotation(JsonFilters. class ).value()); } SimpleFilterProvider filters = new SimpleFilterProvider(); filters.addFilter(JsonFilterAnnotationIntrospector.DEFAULT_FILTER_ID, filter); bodyContainer.setFilters(filters); } } |