I want to pretty print json responses from Spring MVC Restcontrollers dynamically based on a http parameter (like suggested here: http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api#pretty-print-gzip).
I have found configurations for pretty print it by static configuration, but not how to do that dynamically?
When using Spring MVC for REST, how do you enable Jackson to pretty-print rendered JSON?
Any idea how to do that?
You can define a new Media Type, say, application/pretty+json and register a new HttpMessageConverter that converts to that Media Type. In fact, if client sends a request with Accept: application/pretty+json header, our new HttpMessageConverter will write the response, Otherwise, the plain old MappingJackson2HttpMessageConverter would do that.
So, extends the MappingJackson2HttpMessageConverter like following:
public class PrettyPrintJsonConverter extends MappingJackson2HttpMessageConverter {
    public PrettyPrintJsonConverter() {
        setPrettyPrint(true);
    }
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return Collections.singletonList(new MediaType("application", "pretty+json"));
    }
    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        boolean canWrite = super.canWrite(clazz, mediaType);
        boolean canWritePrettily = mediaType != null && 
                                   mediaType.getSubtype().equals("pretty+json");
        return canWrite && canWritePrettily;
    }
}
That setPrettyPrint(true) in constructor will do the trick for us. Then we should register this HttpMessageConverter:
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new PrettyPrintJsonConverter());
    }
}
As i said, if client send a request with application/pretty+json Accept header, our PrettyPrintJsonConverter will write the JSON representation Prettily. Otherwise, MappingJackson2HttpMessageConverter would write a compact JSON to the response body. 
You can achieve the same with a ResponseBodyAdvice or even Interceptors but in my opinion, registering a brand new HttpMessageConverter is the better approach. 
To switch to pretty rendering with a ?pretty=true parameter I use a custom MappingJackson2HttpMessageConverter
@Configuration
@RestController
public class MyController {
@Bean
MappingJackson2HttpMessageConverter currentMappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter jsonConverter = new CustomMappingJackson2HttpMessageConverter();
        return jsonConverter;
}
public static class Input {
    public String pretty;
}
public static class Output {
    @JsonIgnore
    public String pretty;
}
@RequestMapping(path = "/api/test", method = {RequestMethod.GET, RequestMethod.POST})
Output test( @RequestBody(required = false) Input input,
             @RequestParam(required = false, value = "pretty") String pretty)
{
     if (input.pretty==null) input.pretty = pretty;
     Output output = new Output();
     output.pretty = input.pretty;
     return output;
}
}
The converter :
public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
    ObjectMapper objectMapper;
    ObjectMapper prettyPrintObjectMapper;
    public CustomMappingJackson2HttpMessageConverter() {
        objectMapper = new ObjectMapper();
        prettyPrintObjectMapper = new ObjectMapper();
        prettyPrintObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
    }
    @Override
    @SuppressWarnings("deprecation")
    protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
        JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
        try {
            writePrefix(generator, object);
            Class<?> serializationView = null;
            FilterProvider filters = null;
            Object value = object;
            JavaType javaType = null;
            if (object instanceof MappingJacksonValue) {
                MappingJacksonValue container = (MappingJacksonValue) object;
                value = container.getValue();
                serializationView = container.getSerializationView();
                filters = container.getFilters();
            }
            javaType = getJavaType(type, null);
            ObjectMapper currentMapper = objectMapper;
            Field prettyField = ReflectionUtils.findField(object.getClass(), "pretty");
            if (prettyField != null) {
                Object prettyObject = ReflectionUtils.getField(prettyField, object);
                if (prettyObject != null  &&  prettyObject instanceof String) {
                    String pretty = (String)prettyObject;
                    if (pretty.equals("true"))
                        currentMapper = prettyPrintObjectMapper;
                }
            }
            ObjectWriter objectWriter;
            if (serializationView != null) {
                objectWriter = currentMapper.writerWithView(serializationView);
            }
            else if (filters != null) {
                objectWriter = currentMapper.writer(filters);
            }
            else {
                objectWriter = currentMapper.writer();
            }
            if (javaType != null && javaType.isContainerType()) {
                objectWriter = objectWriter.withType(javaType);
            }
            objectWriter.writeValue(generator, value);
            writeSuffix(generator, object);
            generator.flush();
        }
        catch (JsonProcessingException ex) {
            throw new HttpMessageNotWritableException("Could not write content: " + ex.getMessage(), ex);
        }
    }
}
Franck
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With