当使用Spring MVC进行REST时,如何启用Jackson来使呈现的JSON格式更易读?

55
在使用Spring MVC开发REST服务时,我希望在开发环境中将JSON以"漂亮的打印方式"呈现,但在生产环境中不需要这样做(需要减少空格)。

http://jira.codehaus.org/browse/JACKSON-128 - Bozho
128号问题中的信息是否回答了这个问题?与Jersey或@Component或@Get注释无关,但是直接回答问题标题“如何使Jackson漂亮地打印生成的JSON内容?”我在下面发布了一个答案。 - Programmer Bruce
请查看链接 - Marco Lackovic
10个回答

58
如果您正在使用Spring Boot 1.2或更高版本,则简单的解决方案是添加:
spring.jackson.serialization.INDENT_OUTPUT=true

将配置文件application.properties中的内容进行修改。假设您正在使用Jackson进行序列化。

如果您使用的是早期版本的Spring Boot,则可以添加

http.mappers.json-pretty-print=true

这个解决方案仍然适用于Spring Boot 1.2,但是它已经被弃用,最终将完全删除。在启动时,日志中会收到一个弃用警告。

(使用spring-boot-starter-web进行测试)


1
哇,那真是太简单了。谢谢你帮我省下了时间。 - Jeff French
我在2011年提出了这个问题,但是Spring Boot的版本使得它变得更加简单,参见这个答案。我正在改变接受的答案为这个,以便更多的人找到它而不是旧的答案。 - Les Hazlewood

31

在我发布这个问题时,我已经有了一个答案,但我还是决定发布它,以防有更好的替代解决方案。这是我的经验:

首先要明确一件事,MappingJacksonHttpMessageConverter 期望您注入一个 Jackson ObjectMapper 实例,并对该实例进行 Jackson 配置(而不是通过 Spring 类)。

我认为只需执行以下操作即可:

创建一个 ObjectMapperFactoryBean 实现,允许我自定义注入到 MappingJacksonHttpMessageConverter 中的 ObjectMapper 实例。例如:

<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>

然后,在我的ObjectMapperFactoryBean实现中,我可以这样做(正如在SO的其他地方记录的解决方案):

ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, isPrettyPrint());
return mapper;

但它没有起作用。而且试图找出原因是一场噩梦。弄清楚Jackson的工作方式需要耐心和时间的考验。查看其源代码只会让你更加困惑,因为它使用过时和晦涩的配置形式(整数位掩码来开启/关闭功能?你在开玩笑吗?)。

我基本上不得不从头开始重新编写Spring的MappingJacksonHttpMessageConverter,并重写其 writeInternal 实现如下:

@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);
    }
}

我在现有实现的基础上添加的唯一内容是以下代码块:

if (isPrettyPrint()) {
    jsonGenerator.useDefaultPrettyPrinter();
}

isPrettyPrint()只是我在MappingJacksonHttpMessageConverter子类中添加的与JavaBeans兼容的getter和匹配的setter。

仅在完成这些步骤后,我才能基于${json.prettyPrint}值(根据应用程序部署方式设置为属性)打开或关闭漂亮的打印。

希望这能对未来的某个人有所帮助!


2
建议开一个 JIRA 请求,提出这个改进。 - Oliver Drotbohm

24

如果你正在使用Jackson 2.0.0,你可以按照Les想要的方式进行操作。 我目前使用的是RC3,配置似乎正在按预期工作。

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

翻译

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

进入

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

4
或者只需使用jacksonMapper.enable(SerializationFeature.INDENT_OUTPUT); - gertas

23

我可以建议采用这种方法,它适用于Spring 4.0.x甚至早期版本。

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());
    }

}

感谢Willie Wheeler提供的解决方案:Willie Wheeler的Spring博客


1
+1 为 JavaConfig 示例。另外,我认为使用这种方法需要手动注册 Java 8 日期和时间支持。我用 objMapper.registerModule(new JavaTimeModule()); 实现了这一点。欢迎其他人分享最佳实践。 - Chris Everitt

22

如何使Jackson生成的JSON内容更易于阅读?

这里是一个简单的例子:

原始JSON输入:

{"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;}
}

输出:

{
  "one" : "AAA",
  "two" : [ "BBB", "CCC" ],
  "three" : {
    "four" : "DDD",
    "five" : [ "EEE", "FFF" ]
  }
}
如果这种方法不完全符合您的需求,可以搜索API文档v1.8.1中有关“pretty”的相关组件。如果您使用的是API版本2.x,则应查看更新的API 2.1.0文档

1
那个例子对我的情况不相关,恐怕我没有直接使用ObjectMapper和ObjectWriter。 - neu242
1
这对于Spring MVC和Jackson的上下文没有帮助。 - Bobo
2
好的。这回答了这篇帖子标题中的具体问题,对于那些期望得到这样的信息的人来说很有用。 - Programmer Bruce
10
新版本中应该这样写:ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();。 - Eelco
3
这个答案没有涉及到问题中指定的环境。 - Les Hazlewood
是的,我看到问题已经被改变了。当然,最初的问题是:“如何让Jackson漂亮地打印生成的JSON内容?”我的帖子确实回答了那个最初的问题。 - Programmer Bruce

6

要启用漂亮的打印,需要添加并配置MappingJackson2HttpMessageConverter转换器。在生产环境中禁用漂亮的打印。

消息转换器配置

<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>

将 {json.prettyPrint} 设置为 "true" 后,可以与 Spring MVC 一起使用。 - fortm
虽然这解决了我的构建问题,但我正在寻找一种解决方案,其中漂亮的打印是使用请求参数 prettyPrint 渲染/防止的。我该如何实现呢? - mickeymoon

4

根据baeldung的文章,使用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);
    }
}

3

我曾经遇到了使用自定义MappingJacksonHttpMessageConverter时的问题,尽管按照上述建议进行了操作,但在配置方面仍然遇到了一些困难。从代码角度来看,我完全按照上述方法进行了操作,但为了使其正常工作,我不得不在我的springapp-servlet.xml文件中添加以下配置。

希望这可以帮助其他想要实现相同功能的人。

<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>

3
Jackson 2拥有更好的API,这点是同意的,但在Spring MVC环境中它无法解决这个问题,因为Spring MVC使用ObjectMapper#writeValue(JsonGenerator, Object)将对象写成JSON。这个writeValue变体不会应用ObjectMapper序列化特性,如Jackson 1.x或2.0中的INDENT_OUTPUT。
我认为这有些令人困惑。既然我们使用ObjectMapper来构建JsonGenerators,我希望返回的生成器基于配置的ObjectMapper设置进行初始化。我在这里向Jackson 2.0报告了这个问题:https://github.com/FasterXML/jackson-databind/issues/12
Les的建议是根据prettyPrint标志的值调用JsonGenerator#useDefaultPrettyPrinter,目前这是我们能做的最好的事情。我已经创建了一个Jackson2 HttpMessageConverter,根据启用状态的INDENT_OUTPUT SerializationFeature来执行此操作:https://gist.github.com/2423129

1
这已经不适用了。Jackson 2.1.0支持与Spring MVC一起使用的INDENT_OUTPUT功能。 - Ryan Walls

1

我认为这是一个渲染问题,而不是REST服务的问题。

谁负责渲染?让该组件格式化JSON。也许可以有两个网址 - 一个用于生产环境,另一个用于开发环境。


显然这应该是这样的(在我的应用程序中确实如此)。我正在询问如何启用或禁用行为的具体连线方式。(不过没关系,我有一个答案,很快就会发布。) - Les Hazlewood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接