While developing REST services using Spring MVC, I would like render JSON \'pretty printed\' in development but normal (reduced whitespace) in production.
I would make that a rendering issue, not the concern of the REST service.
Who's doing the rendering? Let that component format the JSON. Maybe it can be two URLs - one for production and another for development.
I had trouble getting the custom MappingJacksonHttpMessageConverter to work as suggested above but I was finally able to get it to work after struggling w/ the configuration. From the code stand point I did exactly what was mentioned above but I had to add the following configuration to my springapp-servlet.xml to get it to work.
I hope this helps others who are looking to implement the same.
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jsonConverter" />
</list>
</property>
</bean>
<bean id="jsonConverter" class="com.xxx.xxx.xxx.common.PrettyPrintMappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
<property name="prettyPrint" value="true" />
</bean>
Based on baeldung this could be a nice idea using java 8:
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
Optional<HttpMessageConverter<?>> converterFound;
converterFound = converters.stream().filter(c -> c instanceof AbstractJackson2HttpMessageConverter).findFirst();
if (converterFound.isPresent()) {
final AbstractJackson2HttpMessageConverter converter;
converter = (AbstractJackson2HttpMessageConverter) converterFound.get();
converter.getObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
converter.getObjectMapper().enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
}
Jackson 2 has a nicer API, agreed, but it won't resolve this problem in a Spring MVC environment given Spring MVC uses ObjectMapper#writeValue(JsonGenerator, Object) to write objects out as JSON. This writeValue variant does not apply ObjectMapper serialization features such as INDENT_OUTPUT in either Jackson 1.x or 2.0.
I do think this is somewhat confusing. Since we use the ObjectMapper to construct JsonGenerators, I'd expect returned generators to be initialized based on configured ObjectMapper settings. I reported this as a issue against Jackson 2.0 here: https://github.com/FasterXML/jackson-databind/issues/12.
Les's suggestion of calling JsonGenerator#useDefaultPrettyPrinter based on the value of a prettyPrint flag is about the best we can do at the moment. I've gone ahead and created a Jackson2 HttpMessageConverter that does this based on the enabled status of the INDENT_OUTPUT SerializationFeature: https://gist.github.com/2423129.
If you are using Spring Boot 1.2 or later the simple solution is to add
spring.jackson.serialization.INDENT_OUTPUT=true
to the application.properties
file. This assumes that you are using Jackson for serialization.
If you are using an earlier version of Spring Boot then you can add
http.mappers.json-pretty-print=true
This solution still works with Spring Boot 1.2 but it is deprecated and will eventually be removed entirely. You will get a deprecation warning in the log at startup time.
(tested using spring-boot-starter-web
)
I had an answer when I posted this question, but I thought I'd post it anyway in case there are better alternative solutions. Here was my experience:
First thing's first. The MappingJacksonHttpMessageConverter
expects you to inject a Jackson ObjectMapper
instance and perform Jackson configuration on that instance (and not through a Spring class).
I thought it would be as easy as doing this:
Create an ObjectMapperFactoryBean
implementation that allows me to customize the ObjectMapper
instance that can be injected into the MappingJacksonHttpMessageConverter
. For example:
<bean id="jacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper">
<bean class="com.foo.my.ObjectMapperFactoryBean">
<property name="prettyPrint" value="${json.prettyPrint}"/>
</bean>
</property>
</bean>
And then, in my ObjectMapperFactoryBean
implementation, I could do this (as has been documented as a solution elsewhere on SO):
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, isPrettyPrint());
return mapper;
But it didn't work. And trying to figure out why is a nightmare. It is a major test of patience to figure Jackson out. Looking at its source code only confuses you further as it uses outdated and obtuse forms of configuration (integer bitmasks for turning on/off features? Are you kidding me?)
I essentially had to re-write Spring's MappingJacksonHttpMessageConverter
from scratch, and override its writeInternal
implementation to be the following:
@Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = getEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator jsonGenerator =
getObjectMapper().getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
try {
if (this.prefixJson) {
jsonGenerator.writeRaw("{} && ");
}
if (isPrettyPrint()) {
jsonGenerator.useDefaultPrettyPrinter();
}
getObjectMapper().writeValue(jsonGenerator, o);
}
catch (JsonGenerationException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
The only thing I added to the existing implementation is the following block:
if (isPrettyPrint()) {
jsonGenerator.useDefaultPrettyPrinter();
}
isPrettyPrint()
is just a JavaBeans compatible getter w/ matching setter that I added to my MappingJacksonHttpMessageConverter
subclass.
Only after jumping through these hoops was I able to turn on or off pretty printing based on my ${json.prettyPrint}
value (that is set as a property depending on how the app is deployed).
I hope this helps someone out in the future!