When using Spring MVC for REST, how do you enable Jackson to pretty-print rendered JSON?
Asked Answered
P

10

56

While developing REST services using Spring MVC, I would like render JSON 'pretty printed' in development but normal (reduced whitespace) in production.

Pampuch answered 30/6, 2011 at 22:19 Comment(3)
jira.codehaus.org/browse/JACKSON-128Promoter
Does the info in issue 128 answer the question? Not specific to Jersey or the @Component or @Get annotations, but in direct answer to the question title, "How do I make Jackson pretty-print the JSON content it generates?", I posted an answer below.Divers
See linkWaw
I
59

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)

Illsorted answered 20/9, 2014 at 13:56 Comment(2)
Wow, that was simple. Thanks for saving me the time.Rexferd
I asked this question in 2011, but versions of Spring Boot have made this much simpler, per this answer. I'm changing the accepted answer to this one so that more people find it rather than the older one.Pampuch
P
31

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!

Pampuch answered 30/6, 2011 at 22:49 Comment(1)
It might make sense to open up a JIRA request suggesting that improvement.Florous
H
24

When you are using Jackson 2.0.0, you can do it in a way Les wanted to. I currently use RC3 and the configuration seems to be working as expected.

ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

translates

{"foo":"foo","bar":{"field1":"field1","field2":"field2"}}

into

{
  "foo" : "foo",
  "bar" : {
    "field1" : "field1",
    "field2" : "field2"
  }
}
Hither answered 25/3, 2012 at 18:54 Comment(1)
or just jacksonMapper.enable(SerializationFeature.INDENT_OUTPUT);Walkup
B
23

Might I suggest this approach, it is valid with Spring 4.0.x and possibly older versions.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc    
public class WebMvcConfig extends WebMvcConfigurerAdapter {


    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper());
        return mappingJackson2HttpMessageConverter;
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objMapper = new ObjectMapper();
        objMapper.enable(SerializationFeature.INDENT_OUTPUT);
        return objMapper;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);        
        converters.add(mappingJackson2HttpMessageConverter());
    }

}

Thanks to Willie Wheeler for the solution: Willie Wheeler's Spring blog

Blain answered 8/5, 2014 at 20:47 Comment(1)
+1 for a JavaConfig example. On a seperate note, I think that Java 8 Date and Time support needs to be registered manually with this approach. I did this with objMapper.registerModule(new JavaTimeModule());. I'd welcome others' best practices on this.Childlike
D
22

How do I make Jackson pretty-print the JSON content it generates?

Here's a simple example:

Original JSON Input:

{"one":"AAA","two":["BBB","CCC"],"three":{"four":"DDD","five":["EEE","FFF"]}}

Foo.java:

import java.io.FileReader;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;

public class Foo
{
  public static void main(String[] args) throws Exception
  {
    ObjectMapper mapper = new ObjectMapper();
    MyClass myObject = mapper.readValue(new FileReader("input.json"), MyClass.class);
    // this is Jackson 1.x API only: 
    ObjectWriter writer = mapper.defaultPrettyPrintingWriter();
    // ***IMPORTANT!!!*** for Jackson 2.x use the line below instead of the one above: 
    // ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
    System.out.println(writer.writeValueAsString(myObject));
  }
}

class MyClass
{
  String one;
  String[] two;
  MyOtherClass three;

  public String getOne() {return one;}
  void setOne(String one) {this.one = one;}
  public String[] getTwo() {return two;}
  void setTwo(String[] two) {this.two = two;}
  public MyOtherClass getThree() {return three;}
  void setThree(MyOtherClass three) {this.three = three;}
}

class MyOtherClass
{
  String four;
  String[] five;

  public String getFour() {return four;}
  void setFour(String four) {this.four = four;}
  public String[] getFive() {return five;}
  void setFive(String[] five) {this.five = five;}
}

Output:

{
  "one" : "AAA",
  "two" : [ "BBB", "CCC" ],
  "three" : {
    "four" : "DDD",
    "five" : [ "EEE", "FFF" ]
  }
}

If this approach doesn't exactly fit your needs, if you search the API docs v1.8.1 for "pretty", it'll turn up the relevant components available. If you use API version 2.x then look instead at the newer API 2.1.0 docs.

Divers answered 14/6, 2011 at 5:26 Comment(6)
That example isn't relevant in my case, I'm afraid. I do not use the ObjectMapper and ObjectWriter directly.Chapell
this wont help in the context of spring mvc, jaskcon.Harquebus
Right. This addresses the specific question in the title of this post, for folks that stumble upon this expecting such information.Divers
Should in newer versions be: ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();Loggerhead
This answer does not address the environment specified in the question.Pampuch
Yes, I see that the question was changed. Of course, the original question was, "How do I make Jackson pretty-print the JSON content it generates?" My post does answer that original question.Divers
M
6

Pretty print will be enable by adding and configure the MappingJackson2HttpMessageConverter converter. Disable prettyprint within production environment.

Message converter configuration

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean id="jacksonHttpMessageConverter"
            class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="prettyPrint" value="${json.prettyPrint}" />
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
Malony answered 30/5, 2014 at 11:2 Comment(2)
{json.prettyPrint} after setting to "true" works with Spring MVCClarkia
while this solves my problem at build time but I am looking for a solution where the pretty printing is rendered/prevented using a request parameter prettyPrint. How do I achieve that.Ireland
C
4

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);
    }
}
Commoner answered 16/7, 2016 at 13:8 Comment(0)
S
3

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>
Saskatchewan answered 22/3, 2012 at 7:43 Comment(0)
O
3

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.

Orenorenburg answered 19/4, 2012 at 18:41 Comment(1)
This is no longer applicable. Jackson 2.1.0 supports INDENT_OUTPUT feature with Spring MVC.Baseman
S
1

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.

Strathspey answered 30/6, 2011 at 22:23 Comment(1)
obviously that should be the case (and it is in my app). I was asking for the specific wiring of how to enable or disable the behavior. (It's ok though, I have an answer which I'll post shortly.)Pampuch

© 2022 - 2024 — McMap. All rights reserved.