jackson序列化與反序列化的應用實踐
原始碼地址:
https://github.com/zhouweixin/serializable
1 相關概念
- 序列化: 把物件轉換為位元組序列的過程稱為物件的序列化
- 反序列化: 把位元組序列恢復為物件的過程稱為物件的反序列化
2 序列化的作用
- 用於把記憶體中的物件狀態儲存到一個檔案中或者資料庫中
- 用於網路傳送物件
- 用於遠端呼叫傳輸物件
3 準備序列化物件
準備了兩個類, 教師類和學生類, 其中一個學生只有一個教師
這裡省略了構造方法和setter, getter方法
Teacher.java
public class Teacher {
private String name;
private Integer age;
}
Student.java
package org.zwx;
public class Student {
private String name;
private Integer age;
private Sex sex;
private String fatherName;
private Date bornTime;
private Teacher teacher;
}
Sex.java
public enum Sex {
MALE("男"), FEMALE("女");
private String name;
Sex(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
4 引入jackson依賴
本示例是基於gradle的, 從maven中心倉庫中選擇了2.11.2版本的jackson-databind
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.11.2'
5 序列化與格式化輸出
5.1 流程
- 首先需要有一個待序列化物件, 本例中的student物件
- 建立一個物件對映器, jackson包下的ObjectMapper
- 呼叫序列化函式, 本例中的writeValueAsString, 將物件轉為字串, 便於展示
5.2 程式碼
public void testSerializable() throws IOException {
Student student1 = new Student("小明", 18, Sex.MALE, "王富貴", new Date(), new Teacher("李老師", 40));
Student student2 = new Student("小花", 16, Sex.FEMALE, "錢很多", new Date(), new Teacher("趙老師", 38));
List<Student> students = new ArrayList<>();
students.add(student1);
students.add(student2);
ObjectMapper mapper = new ObjectMapper();
String s = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(students);
System.out.println(s);
}
5.3 結果
[ {
"name" : "小明",
"age" : 18,
"sex" : "MALE",
"fatherName" : "王富貴",
"bornTime" : 1599996926917,
"teacher" : {
"name" : "李老師",
"age" : 40
}
}, {
"name" : "小花",
"age" : 16,
"sex" : "FEMALE",
"fatherName" : "錢很多",
"bornTime" : 1599996926917,
"teacher" : {
"name" : "趙老師",
"age" : 38
}
} ]
5.4 分析
- 示例中呼叫了方法writerWithDefaultPrettyPrinter, 美化了json的格式
- 否則將列印
[{"name":"小明","age":18,"sex":"MALE","fatherName":"王富貴","bornTime":1599997061097,"teacher":{"name":"李老師","age":40}},{"name":"小花","age":16,"sex":"FEMALE","fatherName":"錢很多","bornTime":1599997061097,"teacher":{"name":"趙老師","age":38}}]
6 自定義序列化的名字
6.1 場景
假如需要將序列化的json由駝峰命名修改為下劃線命名, 如fatherName修改為father_name
只需要在欄位fatherName上用註解JsonProperty配置
6.2 示例程式碼
@JsonProperty("father_name")
private String fatherName;
@JsonProperty("born_time")
private Date bornTime;
6.3 示例結果
[ {
"name" : "小明",
"age" : 18,
"sex" : "MALE",
"teacher" : {
"name" : "李老師",
"age" : 40
},
"father_name" : "王富貴",
"born_time" : 1599997157609
}, {
"name" : "小花",
"age" : 16,
"sex" : "FEMALE",
"teacher" : {
"name" : "趙老師",
"age" : 38
},
"father_name" : "錢很多",
"born_time" : 1599997157610
} ]
7 自定義輸出格式
7.1 bornTime格式設定
當前bornTime的格式為unix時間戮, 可讀性非常差
現修改為yyyy-MM-dd HH:mm:ss
並設定時區為東八區
示例程式碼
@JsonProperty("born_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date bornTime;
結果
[ {
"name" : "小明",
"age" : 18,
"sex" : "MALE",
"teacher" : {
"name" : "李老師",
"age" : 40
},
"father_name" : "王富貴",
"born_time" : "2020-09-13 19:50:47"
}, {
"name" : "小花",
"age" : 16,
"sex" : "FEMALE",
"teacher" : {
"name" : "趙老師",
"age" : 38
},
"father_name" : "錢很多",
"born_time" : "2020-09-13 19:50:47"
} ]
7.2 sex設定為中文
只需要為Sex新增一個方法getOrdinal, 並新增註解JsonValue即可
示例程式碼
@JsonValue
public String getOrdinal() {
return name;
}
示例結果
[ {
"name" : "小明",
"age" : 18,
"sex" : "男",
"teacher" : {
"name" : "李老師",
"age" : 40
},
"father_name" : "王富貴",
"born_time" : "2020-09-13 19:57:47"
}, {
"name" : "小花",
"age" : 16,
"sex" : "女",
"teacher" : {
"name" : "趙老師",
"age" : 38
},
"father_name" : "錢很多",
"born_time" : "2020-09-13 19:57:47"
} ]
7.3 sex設定為序號
有些場景喜歡用0和1等序號設定男女, 即列舉的序號: 0表示男, 1表示女
此時需要修改Set的getOrdinal方法
- 修改返回值型別為int
- 呼叫父類的getOrdinal方法
示例程式碼
@JsonValue
public int getOrdinal() {
return super.ordinal();
}
示例結果
[ {
"name" : "小明",
"age" : 18,
"sex" : 0,
"teacher" : {
"name" : "李老師",
"age" : 40
},
"father_name" : "王富貴",
"born_time" : "2020-09-13 20:01:44"
}, {
"name" : "小花",
"age" : 16,
"sex" : 1,
"teacher" : {
"name" : "趙老師",
"age" : 38
},
"father_name" : "錢很多",
"born_time" : "2020-09-13 20:01:44"
} ]
8 拍平巢狀型別
場景
如前面提到的結果所示, teacher的兩個屬性並不在student的第一層,
有時可能會更深的層次, 使用起來不太友好
如何用teacher_name和teacher_age兩個屬性代替teacher呢?
- 在Student的teacher屬性上新增註解JsonUnwrapped, 意為不包裹
- 在Teacher的屬性上利用註解JsonProperty重新命名
示例程式碼
Student.java
@JsonUnwrapped
private Teacher teacher;
Teacher.java
@JsonProperty("teacher_name")
private String name;
@JsonProperty("teacher_age")
private Integer age;
示例結果
[ {
"name" : "小明",
"age" : 18,
"sex" : 0,
"teacher_name" : "李老師",
"teacher_age" : 40,
"father_name" : "王富貴",
"born_time" : "2020-09-13 20:21:53"
}, {
"name" : "小花",
"age" : 16,
"sex" : 1,
"teacher_name" : "趙老師",
"teacher_age" : 38,
"father_name" : "錢很多",
"born_time" : "2020-09-13 20:21:53"
} ]
9 自定義序列化器
9.1 場景
假如需要將年齡調整為理論學齡, 即將年齡減去7, 得到理論學齡, 如何操作呢?
- 建立自定義年齡序列化器AgeSerializer, 繼承StdSerializer<>
- 建立AgeSerializer的構造方法
- 重寫serialize函式
- 利用註解修指定Student屬性age的序列化器AgeSerializer
9.2 示例程式碼
AgeSerializer.java
public class AgeSerializer extends StdSerializer<Integer> {
protected AgeSerializer() {
super(Integer.class);
}
@Override
public void serialize(Integer value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeNumber(value - 7);
}
}
Student.java
@JsonSerialize(using = AgeSerializer.class)
private Integer age;
9.3 示例結果
[ {
"name" : "小明",
"age" : 11,
"sex" : 0,
"teacher_name" : "李老師",
"teacher_age" : 40,
"father_name" : "王富貴",
"born_time" : "2020-09-13 20:31:59"
}, {
"name" : "小花",
"age" : 9,
"sex" : 1,
"teacher_name" : "趙老師",
"teacher_age" : 38,
"father_name" : "錢很多",
"born_time" : "2020-09-13 20:31:59"
} ]
10 反序列化
10.1 流程
- 首先需要有序列化好的資料, 可以是string, byte[], 檔案二進位制等
- 建立一個物件對映器, jackson包下的ObjectMapper
- 呼叫反序列化函式, 本例中的readValue, 將字串轉為物件
10.2 反序列化物件資料
示例程式碼
public void testDeserializable() throws JsonProcessingException {
String s = "{\"name\":\"小明\",\"age\":11,\"sex\":0,\"teacher_name\":\"李老師\",\"teacher_age\":40,\"father_name\":\"王富貴\",\"born_time\":\"2020-09-13 20:46:10\"}";
ObjectMapper mapper = new ObjectMapper();
Student student = mapper.readValue(s, Student.class);
System.out.println(student);
}
示例結果
Student{name='小明', age=11, sex=MALE, fatherName='王富貴', bornTime=Sun Sep 13 20:46:10 CST 2020, teacher=Teacher{name='李老師', age=40}}
分析
- 為了便於列印物件資料, 重寫了Student和Teacher的toString方法
- 從資料中可以看出, age的結果是錯誤的, 原因在於之前自定義的序列化器將年齡減小了7, 10.4節將會通過自定義反序列化器來解決此問題
10.3 反序列化物件陣列資料
示例程式碼
public void testDeserializableStudents() throws JsonProcessingException {
String s = "[{\"name\":\"小明\",\"age\":11,\"sex\":0,\"teacher_name\":\"李老師\",\"teacher_age\":40,\"father_name\":\"王富貴\",\"born_time\":\"2020-09-13 20:51:31\"},{\"name\":\"小花\",\"age\":9,\"sex\":1,\"teacher_name\":\"趙老師\",\"teacher_age\":38,\"father_name\":\"錢很多\",\"born_time\":\"2020-09-13 20:51:31\"}]";
ObjectMapper mapper = new ObjectMapper();
Student[] students = mapper.readValue(s, Student[].class);
for (Student student : students) {
System.out.println(student);
}
}
示例結果
Student{name='小明', age=11, sex=MALE, fatherName='王富貴', bornTime=Sun Sep 13 20:51:31 CST 2020, teacher=Teacher{name='李老師', age=40}}
Student{name='小花', age=9, sex=FEMALE, fatherName='錢很多', bornTime=Sun Sep 13 20:51:31 CST 2020, teacher=Teacher{name='趙老師', age=38}}
分析
- readValue的第二個引數需要傳型別, 這裡推薦用陣列, 不推薦用List, 具體原因筆者目前也沒花時間去研究
10.4 自定義反序列化器
從10.2節及10.3的現象中可以看出來, 僅僅自定義的序列化器會導致序列化的過程是正常的, 反序列化的過程仍然是預設邏輯, 有時候會導致意想不到的結果
遇到此場景, 可以考慮自定義反序列化器
- 建立自定義反序列化器AgeDeserializer, 繼承StdDeserializer<>
- 重寫deserialize方法
- 在Student的age屬性上新增註解JsonDeserialize, 並指定反序列化器AgeDeserializer
示例程式碼
AgeDeserializer.java
public class AgeDeserializer extends JsonDeserializer<Integer> {
@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return p.getIntValue() + 7;
}
}
Student.java
@JsonSerialize(using = AgeSerializer.class)
@JsonDeserialize(using = AgeDeserializer.class)
private Integer age;
示例結果
Student{name='小明', age=18, sex=MALE, fatherName='王富貴', bornTime=Sun Sep 13 20:51:31 CST 2020, teacher=Teacher{name='李老師', age=40}}
Student{name='小花', age=16, sex=FEMALE, fatherName='錢很多', bornTime=Sun Sep 13 20:51:31 CST 2020, teacher=Teacher{name='趙老師', age=38}}
11 註解JsonInclude
該註解使用在實體類上, 格式@JsonInclude(value = JsonInclude.Include.NON_DEFAULT)
其中, Include有7種引數, 功能對比如下
引數 | 功能 | 備註 |
---|---|---|
Include.ALWAYS | 屬性總是序列化(需要有get方法) | 預設值 |
Include.NON_DEFAULT | 屬性為預設值不序列化 | 如: int:0, bool:false |
Include.NON_EMPTY | 屬性為空("")或null不序列化 | |
Include.NON_NULL | 屬性為null不序列化 | |
Include.CUSTOM | ||
Include.USE_DEFAULTS | ||
Include.NON_ABSENT |
程式碼示例
Student.java
@JsonInclude(value = JsonInclude.Include.NON_DEFAULT)
public class Student {
public void testNonDefault() throws IOException {
Student student = new Student("", 0, null, null, null, null);
ObjectMapper mapper = new ObjectMapper();
String s = mapper.writeValueAsString(student);
System.out.println(s);
}
示例輸出
{
"name" : "",
"age" : -7
}
分析
- 當屬性為預設值, 即零值時, 不序列化
- 常見的零值:
- int: 0
- bool: false,
- String: null