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参数中。

/**
 * 反序列化时,使用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/


---转载本站文章请注明作者和出处 996极客教程(996geek.com),请勿用于任何商业用途---

留下评论