Jackson教程
序列化集合和数组

在入门配置《Jackson入门配置及示例》一讲中,我们使用Jackson对普通对象进行了序列化处理,操作起来很简单。

除了处理普通对象,Jackson还可以对Java集合、数组等进行序列化处理。

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

元素为基本类型

List的序列化

和处理普通对象一样,对List的序列化和反序列化,也分别是通过writeValue*方法和readValue方法来实现的。

/**
 * List序列化与反序列化
 *  
 * @throws IOException
 */
@Test
public void serializeList() throws IOException {
	List<String> list = new ArrayList<>();
	list.add("sam");
	list.add("fanny");
	System.out.println("List对象:" + list);
 
	ObjectMapper mapper = new ObjectMapper();
	
	// 将List对象序列化
	System.out.println("List对象序列化:" + mapper.writeValueAsString(list));
	
	// 将数组对应的字符串反序列化为List对象
	String str = "[\"sam2\",\"fanny2\"]";
	System.out.println("反序列化为List对象:" + mapper.readValue(str, List.class));
}

执行结果:

List对象:[sam, fanny]
List对象序列化:["sam","fanny"]
反序列化为List对象:[sam2, fanny2]

Set的序列化

Set的序列化操作和List非常相似,主要差异是Set本身是不重复且无序的,而List是可重复且有序的。

/**
 * Set序列化与反序列化
 *  
 * @throws IOException
 */
@Test
public void serializeSet() throws IOException {
	Set<String> set = new HashSet<>();
	set.add("sam");
	set.add("fanny");
	System.out.println("Set对象:" + set);
	
	ObjectMapper mapper = new ObjectMapper();
	
	// 将Set对象序列化
	System.out.println("Set对象序列化:" + mapper.writeValueAsString(set));
	
	// 将数组对应的字符串反序列化为Set对象
	String str = "[\"sam2\",\"fanny2\"]";
	System.out.println("反序列化为Set对象:" + mapper.readValue(str, Set.class));
}

执行结果:

Set对象:[fanny, sam]
Set对象序列化:["fanny","sam"]
反序列化为Set对象:[sam2, fanny2]

Map的序列化

使用同样的方法,对Map进行序列化处理。

/**
 * Map序列化与反序列化
 *  
 * @throws IOException
 */
@Test
public void serializeMap() throws IOException {
	Map<String, Integer> map = new HashMap<>();
	map.put("sam", 26);
	map.put("fanny", 1);
	System.out.println("Map对象:" + map);
	
	ObjectMapper mapper = new ObjectMapper();
	
	// 将Map对象序列化
	System.out.println("Map对象序列化:" + mapper.writeValueAsString(map));
	
	// 将数组对应的字符串反序列化为Map对象
	String str = "{\"fanny\":1,\"sam\":26}";
	System.out.println("反序列化为Map对象:" + mapper.readValue(str, Map.class));
}

执行结果:

Map对象:{fanny=1, sam=26}
Map对象序列化:{"fanny":1,"sam":26}
反序列化为Map对象:{fanny=1, sam=26}

数组的序列化

使用同样的方法,对数组进行序列化处理。

/**
 * 数组的序列化与反序列化
 *  
 * @throws IOException
 */
@Test
public void serializeArray() throws IOException {
	String[] array = {"sam", "fanny"};
	
	ObjectMapper mapper = new ObjectMapper();
	
	// 将数组对象序列化
	System.out.println("数组对象序列化:" + mapper.writeValueAsString(array));
	
	// 将数组对应的字符串反序列化为数组对象
	String str = "[\"sam2\",\"fanny2\"]";
	String[] newArray = mapper.readValue(str, String[].class);
	System.out.println("反序列化为数组对象. size: " + newArray.length +
			", value: [" + newArray[0] + "," + newArray[1] + "]");
}

执行结果:

数组对象序列化:["sam","fanny"]
反序列化为数组对象. size: 2, value: [sam2,fanny2]

元素为自定义类型

如果集合的元素为非基本类型,那么在进行反序列化后,结果可能不符合预期。

首先,自定义一个类:

public class People {
	private String name;
	private int age;
 
	// 此处省略了getter和setter方法
 
	@Override
	public String toString() {
		return "People [name=" + name + ", age=" + age + "]";
	}
}

接着进行反序列化:

/**
 * 如果集合的元素为非基本类型,那么在进行反序列化后,结果可能不符合预期。
 * 
 * @throws IOException
 */
@Test
public void deserializeNonPrimitive() throws IOException {
	People p1 = new People();
	p1.setName("sam");
	p1.setAge(26);
 
	List<People> list = new ArrayList<>();
	list.add(p1);
	
	ObjectMapper mapper = new ObjectMapper();
	String jsonString = mapper.writeValueAsString(list); // [{"name":"sam","age":26}]
	
	// 下面看看反序列化的效果
	
	List<People> newList = mapper.readValue(jsonString, List.class);
//	List<People> newList = mapper.readValue(jsonString, List<People>.class); // 错误的泛型用法
		
	// 正常输出[{name=sam, age=26}]
	System.out.println(newList.toString());
	
	// 抛出异常:java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to People
	System.out.println(newList.get(0).toString());
}

执行结果:

[{name=sam, age=26}]

然后抛出异常:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to cn.javaee.util.jackson.bean.People
	at cn.javaee.util.jackson.Jackson02.deserializeNonPrimitive(Jackson02.java:139)
...

为什么newList.toString()能够正常输出,而newList.get(0).toString()却会抛出异常呢?

从异常栈可以看到,反序列化后List中的元素类型为LinkedHashMap,而不是People。

这是因为在反序列化的时候,指定的类型为List.class,并没有明确元素的类型,因此Jackson默认使用了Map来对KV值进行反序列化。

那么,把List.class改为List<People>.class可以吗?答案是否定的,因为在Java中是不支持这种用法的,这与泛型的类型擦除规则有关。

通过TypeReference解决类型无法传递问题

为了解决这个问题,Jackson引入了一个抽象类TypeReference,用来传递这种复杂的泛型类型。

TypeReference类的声明带有一个泛型类型:

public abstract class TypeReference<T> implements Comparable<TypeReference<T>> {...}

其基本用法,是创建一个空实现的实例,T传入需要反序列化对应类的class对象。 然后将该实例传入readValue的TypeReference<T>参数中。

/**
 * 反序列化时,使用TypeReference传递复杂的泛型类型
 * 
 * @throws IOException
 */
@Test
public void deserializeNonPrimitiveWithTypeReference() throws IOException {
	ObjectMapper mapper = new ObjectMapper();
	
	// 反序列化List<People>
	String jsonString = "[{\"name\":\"sam\",\"age\":26}]";
	List<People> newList = mapper.readValue(jsonString, new TypeReference<List<People>>(){});
	System.out.println("List:" + newList.get(0).toString());
	
	// 反序列化Map<String, People>
	String jsonString2 = "{\"xiaoming\":{\"name\":\"cy\",\"age\":1},\"xiaoli\":{\"name\":\"fanny\",\"age\":20}}";
	Map<String, People> map = mapper.readValue(jsonString2, new TypeReference<Map<String, People>>(){});
	People p1 = map.get("xiaoming");
	People p2 = map.get("xiaoli");
	System.out.println("xiaoming:" + p1.toString());
	System.out.println("xiaoli:" + p2.toString());
}

执行结果:

List:People [name=sam, age=26]
xiaoming:People [name=cy, age=1]
xiaoli:People [name=fanny, age=20]

TypeReference类的基本思想,来源于这篇文章:http://gafter.blogspot.com/2006/12/super-type-tokens.html

小结

Jackson除了处理普通对象,还可以对Java集合、数组等进行序列化处理。

如果需要"反序列化集合"的元素为非基本类型,可以通过创建一个空实现的TypeReference实例,将需要反序列化的集合带上泛型信息传递进去,以解决泛型信息无法传递的问题。

参考

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