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