Jackson教程
常用的序列化反序列化配置

Jackson内置了许多功能配置项,利用好这些配置项,可以简单高效的解决一些常见的序列化问题。

接下来,介绍几个常用的配置项,在序列化和反序列化时使用。

本篇内容基于Jackson 2.11.2版本,马上开始学习吧。

序列化

定义一个用于序列化的类。

public class Animal {
 
	private String name;
	private int sex;
	private Integer weight;
	private People owner;
	
	public class People {
		private String name;
		private int age;
 
		// 省略getter、setter方法
 
		@Override
		public String toString() {
			return "People [name=" + name + ", age=" + age + "]";
		}
	}
	
	// 省略getter、setter方法
 
	@Override
	public String toString() {
		return "Animal [name=" + name + ", sex=" + sex + ", weight=" + weight + ", owner=" + owner + "]";
	}
 
}

格式化输出

默认情况下,序列化后的内容,都在同一行。当内容比较长时,不便于阅读理解。

通过启用SerializationFeature.INDENT_OUTPUT缩进输出配置,可以使得内容格式化后再输出,非常友好。

/**
 * 启用缩进输出,对JSON字符串进行格式化
 * 
 * @throws JsonProcessingException
 */
@Test
public void indentOutput() throws JsonProcessingException {
	Animal animal = new Animal();
	animal.setName("sam");
	animal.setSex(1);
	animal.setWeight(100);
	
	People owner = animal.new People();
	animal.setOwner(owner);
	
	ObjectMapper mapper = new ObjectMapper();
	mapper.enable(SerializationFeature.INDENT_OUTPUT); // 格式化
	System.out.println(mapper.writeValueAsString(animal));
}

执行结果:

{
  "name" : "sam",
  "sex" : 1,
  "weight" : 100,
  "owner" : {
    "name" : null,
    "age" : 0
  }
}

序列化空Bean

当序列化一个空Bean(Bean没有字段,或者没有public字段和getter方法)时,默认会抛出异常。

示例1:

public class AnimalEmpty {
}
 
/**
 * 默认序列化空Bean时会抛出异常
 * 
 * @throws JsonProcessingException
 */
@Test
public void defaultEmptyBean() throws JsonProcessingException {
	AnimalEmpty animal = new AnimalEmpty();
	ObjectMapper mapper = new ObjectMapper();
	System.out.println(mapper.writeValueAsString(animal));
}

示例2:

public class AnimalNoPublic {
 
	private String name;
	protected int sex;
	Integer weight;
 
	protected int getSex() {
		return sex;
	}
 
	Integer getWeight() {
		return weight;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public void setSex(int sex) {
		this.sex = sex;
	}
 
	public void setWeight(Integer weight) {
		this.weight = weight;
	}
 
}
 
/**
 * 默认序列化空Bean时会抛出异常
 * 
 * @throws JsonProcessingException
 */
@Test
public void defaultNoPublicBean() throws JsonProcessingException {
	AnimalNoPublic animal = new AnimalNoPublic();
	animal.setName("sam");
	animal.setSex(1);
	animal.setWeight(100);
	
	ObjectMapper mapper = new ObjectMapper();
	System.out.println(mapper.writeValueAsString(animal));
}

两个示例的执行结果:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class cn.javaee.util.jackson.config.AnimalNoPublic and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
...

异常信息提示, 为了避免抛出异常,可以禁用SerializationFeature.FAIL_ON_EMPTY_BEANS配置。

/**
 * 允许序列化空Bean
 * 
 * @throws JsonProcessingException
 */
@Test
public void allowEmptyBean() throws JsonProcessingException {
	AnimalNoPublic animal = new AnimalNoPublic();
	animal.setName("sam");
	animal.setSex(1);
	animal.setWeight(100);
	
	ObjectMapper mapper = new ObjectMapper();
	mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); // 允许序列化空Bean
	System.out.println(mapper.writeValueAsString(animal));
}

执行结果:

{}

日期时间戳输出

默认情况下,日期会序列化为数字类型的时间戳。

/**
 * 默认日期序列化为数字类型的时间戳
 * 
 * @throws JsonProcessingException
 */
@Test
public void defaultDate() throws JsonProcessingException {
	List<Date> list = new ArrayList<>();
	list.add(new Date());
	
	ObjectMapper mapper = new ObjectMapper();
	System.out.println(mapper.writeValueAsString(list));
}

执行结果:

[1601995178051]

通过禁用SerializationFeature.WRITE_DATES_AS_TIMESTAMPS檡,禁止将日期序列化为数字类型的时间戳。

/**
 * 禁止将日期序列化为数字类型的时间戳. 默认输出格式:yyyy-MM-dd'T'HH:mm:ss.SSSX
 * 
 * @throws JsonProcessingException
 */
@Test
public void disableTimestamp() throws JsonProcessingException {
	List<Date> list = new ArrayList<>();
	list.add(new Date());
	
	ObjectMapper mapper = new ObjectMapper();
	mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
	System.out.println(mapper.writeValueAsString(list));
}

执行结果:

["2020-10-06T14:40:01.866+00:00"]

Jackson全局配置和JsonFormat注解设置日期格式 (opens in a new tab)一讲中提到,为字段添加@JsonFormat(shape=Shape.STRING)注解后,则日期输出会变成字符串格式,这个字符串格式和本例是相同的。

反序列化

不存在的字段

默认情况下,当反序列化的字段在目标类中不存在时,会抛出异常。

/**
 * 字段weight2不存在,反序列化时将会抛出异常
 * 
 * @throws JsonMappingException
 * @throws JsonProcessingException
 */
@Test
public void unknownProperty() throws JsonMappingException, JsonProcessingException {
	String jsonString = "{\"name\":\"sam\",\"sex\":1,\"weight2\":100}";
	
	ObjectMapper mapper = new ObjectMapper();
	Animal animal = mapper.readValue(jsonString, Animal.class);
	System.out.println(mapper.writeValueAsString(animal));
}

抛出异常:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "weight2" (class cn.javaee.util.jackson.config.Animal), not marked as ignorable (4 known properties: "weight", "name", "sex", "owner"])
 at [Source: (String)"{"name":"sam","sex":1,"weight2":100}"; line: 1, column: 36] (through reference chain: cn.javaee.util.jackson.config.Animal["weight2"])
...

通过禁用DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES配置,在反序列化遇到未知字段时不抛出异常。

/**
 * 遇到不存在的字段时不抛异常
 * 
 * @throws JsonMappingException
 * @throws JsonProcessingException
 */
@Test
public void allowUnknownProperty() throws JsonMappingException, JsonProcessingException {
	String jsonString = "{\"name\":\"sam\",\"sex\":1,\"weight2\":100}";
	
	ObjectMapper mapper = new ObjectMapper();
	mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // 禁用
	
	Animal animal = mapper.readValue(jsonString, Animal.class);
	System.out.println(mapper.writeValueAsString(animal));
}

执行结果:

{"name":"sam","sex":1,"weight":null,"owner":null}

空字符串映射为null值POJO对象

定义两个简单类。

public class Student {
 
	private String name;
	private int age;
 
	// 省略getter、setter方法	
}
 
public class Teacher {
 
	private Student student;
 
	// 省略getter、setter方法	
}

默认情况下,如果反序列化Teacher时,传入的student值为空字符串"",那么将会抛出异常。

/**
 * 抛异常
 * 
 * @throws JsonMappingException
 * @throws JsonProcessingException
 */
@Test
public void emptyStringToPOJO() throws JsonMappingException, JsonProcessingException {
	String jsonString = "{\"student\":\"\"}";
	
	ObjectMapper mapper = new ObjectMapper();
	Teacher teacher = mapper.readValue(jsonString, Teacher.class);
	System.out.println(mapper.writeValueAsString(teacher));
}

抛出异常:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `cn.javaee.util.jackson.config.Student` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('')
...

通过启用DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT配置,可以将空字符串""转化为null值的POJO对象。

/**
 * 正确的将空字符串""转换为null值的Student对象
 * 
 * @throws JsonMappingException
 * @throws JsonProcessingException
 */
@Test
public void emptyStringToNullPOJO() throws JsonMappingException, JsonProcessingException {
	String jsonString = "{\"student\":\"\"}";
	
	ObjectMapper mapper = new ObjectMapper();
	mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
	
	Teacher teacher = mapper.readValue(jsonString, Teacher.class);
	System.out.println(mapper.writeValueAsString(teacher));
}

执行结果:

{"student":null}

另一个例子,Map的值为POJO,当POJO的值为空字符串时转化为null。

/**
 * Map的值为POJO,当POJO的值为空字符串时转化为null
 * 
 * @throws JsonMappingException
 * @throws JsonProcessingException
 */
@Test
public void emptyStringToMapValue() throws JsonMappingException, JsonProcessingException {
	String jsonString = "{\"sam\":\"\"}";
	
	ObjectMapper mapper = new ObjectMapper();
	mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
	
	Map<String, Student> map = mapper.readValue(jsonString, new TypeReference<Map<String, Student>>(){});
	System.out.println(map);
}

执行结果:

{sam=null}

如果试图将空字符串""映射为null值的String字段,结果将出乎意料。

/**
 * 并不支持将空字符串""转换为null值的String字段
 * 
 * @throws JsonMappingException
 * @throws JsonProcessingException
 */
@Test
public void convertFail() throws JsonMappingException, JsonProcessingException {
	String jsonString = "{\"name\":\"\",\"age\":26}";
	
	ObjectMapper mapper = new ObjectMapper();
	mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
	Student student = mapper.readValue(jsonString, Student.class);
	System.out.println(mapper.writeValueAsString(student));
}

执行结果:

{"name":"","age":26}

结果中的name仍为空字符串"",而不是null。这是该配置项比较难理解的地方,容易引起概念上的混淆。

按照官方github上cowtowncoder的回答,ACCEPT_EMPTY_STRING_AS_NULL_OBJECT命名ACCEPT_EMPTY_STRINGS_AS_NULL_POJO可能更好理解,因为该配置针对的是空字符串""与POJO对象之间的映射转换,并不支持String对象类型。

小结

Jackson提供了许多序列化和反序列化配置项,本篇讲了5种常用的配置:

  • 序列化时格式化输出,增加可读性 ;
  • 序列化空Bean时不抛出异常;
  • 序列化日期禁用数字类型的时间戳输出,采用默认的字符串格式;
  • 反序列化遇到不存在的字段时不抛出异常;
  • 反序列化空字符串""映射为null值的POJO对象。

参考

https://github.com/FasterXML/jackson (opens in a new tab)

https://github.com/FasterXML/jackson-databind/issues/1563 (opens in a new tab)

https://github.com/FasterXML/jackson-dataformat-csv/issues/112 (opens in a new tab)

https://stackoverflow.com/questions/22688713/jackson-objectmapper-deserializationconfig-feature-accept-empty-string-as-null-o (opens in a new tab)