使用低版本Jackson 2的类级@JsonInclude包含策略的bug

本文出处:http://blog.csdn.net/chaijunkun/article/details/45110623,转载请注明。由于本人不定期会整理相关博文,会对相应内容作出完善。因此强烈建议在原始出处查看此文。


Jackson是Java语言中非常好用的对象与JSON相互转换的工具。然而本人的一次使用过程中发现了其老版本在某些情况下没有按照我们既定的序列化策略来生成JSON。本文将以使用过程中的例子来说明这一问题并给出相应的解决方法。


首先例子中要实现的功能是将对象转换为JSON时(序列化过程),如果对象的某个属性值为null,则该属性不参与序列化,生成的JSON结果也不会包含该属性。


如果你使用过Jackson,尤其是Jackson 2,就知道这样一个功能可以通过设置ObjectMapper对象的SerializationInclusion策略来实现:

ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);

然而巧合的是,将要被序列化的对象其中有一个字段需要使用特别的序列化方式,先来看一下这个类的定义:

/**
 * 动物bean
 * @author chaijunkun
 * @since 2015年4月18日
 */
//此类级注解暂时不启用,待文中提到时再启用
//@JsonInclude(Include.NON_NULL)
public class Animal {
	
	/** 名称 */
	private String name;
	
	/** 性别 */
	@JsonSerialize(using = BooleanSerializer.class)
	private Boolean sex;

	//getters and setters

}
在Jackson中,对于Boolean类型sex的属性,序列化结果为{"sex":true}或者{"sex":false}。但我希望当sex为true时,序列化结果为1,否则为0,即{"sex":1}或者{"sex":0}。这就需要自定义一个Boolean类型的序列化器来解决:

/**
 * 自定义布尔序列化器
 * @author chaijunkun
 * @since 2015年4月18日
 */
public class BooleanSerializer extends JsonSerializer<Boolean> {

	@Override
	public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
		if (Boolean.FALSE.equals(value)){
			jgen.writeNumber(0);
		}else{
			jgen.writeNumber(1);
		}
	}
	
}
为了验证可用性,编写了如下的单元测试:

public class JacksonTest {
	
	private static final Logger logger = LoggerFactory.getLogger(JacksonTest.class);
	
	@Test
	public void doTest() throws IOException{
		ObjectMapper mapper = new ObjectMapper();
		mapper.setSerializationInclusion(Include.NON_NULL);
		StringWriter sw = null;
		String json = null;
		try{
			Animal animal = new Animal();
			//代码块:1,设置name和sex属性
			{
				animal.setName("dog");
				animal.setSex(true);
			}
			//代码块:2,只设置name属性
			{
				animal.setName("dog");
			}
			//代码块:3,什么也不设置
			{}
			sw = new StringWriter();
			JsonGenerator generator = mapper.getFactory().createGenerator(sw);
			generator.writeObject(animal);
			
			json = sw.toString();
			generator.close();
		}finally {
			IOUtils.closeQuietly(sw);
		}
		logger.info(json);
	}
}
首先把代码块2和3都屏蔽掉,保留代码块1,运行结果为:

{"name":"dog","sex":1}
结果看起来挺正常的。现在单独启用代码块2、代码块3,分别得到的结果为:

{"name":"dog","sex":null}
{"sex":null}

问题出现了,之前不是设置了默认的序列化策略,空属性不参与序列化吗?为何sex字段还是输出null了?原因在于我们对sex字段使用了@JsonSerializer注解。经过查看源码发现,该注解除了using参数外,还有一个include参数,该参数可以不设置,但是隐含的默认值为Inclusion.ALWAYS,也就是说该属性总是参与序列化。那么有没有办法屏蔽掉它呢?可以通过显式设置注解中include参数为Inclusion.NON_NULL来实现,不过在Jackson 2中已经不推荐这种使用方法,而是推荐在属性上增加@JsonInclude(Include.NON_NULL)注解来实现。


@JsonInclude(Include.NON_NULL)注解固然好用,但是每一个属性上都标注上这样一个注解很累,有没有更好的实践呢?当然有,它支持类级注解,只要将它写在类定义上即可实现所有属性为null时都不参与序列化。OK,问题来了,类级注解到底好不好用呢?还是那个单元测试,只启用代码块2,来看下运行结果:

{"name":"dog","sex":null}
坑爹啦!居然没起作用!空的sex字段仍然输出了。来看下我使用的Jackson 2版本

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.1.0</version>
</dependency>
是一个相对老的版本,在http://mvnrepository.com/上看到有版本更新,目前的最新版本是2.5.2,遂更新了下试试,果然问题解决了,单独运行代码块2后的输出结果:

{"name":"dog"}

后来经过反复测试,能够支持@JsonInclude注解类级使用的最低版本为2.3.0-rc1,当然如果不考虑候选版本的话,正式版的最低要求为2.3.0。所以,赶快升级你的Jackson 2依赖吧!

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。