jackson序列化與反序列化的應用實踐

jackson序列化與反序列化的應用實踐

源碼地址:
https://github.com/zhouweixin/serializablejava

1 相關概念

  1. 序列化: 把對象轉換爲字節序列的過程稱爲對象的序列化
  2. 反序列化: 把字節序列恢復爲對象的過程稱爲對象的反序列化

2 序列化的做用

  1. 用於把內存中的對象狀態保存到一個文件中或者數據庫中
  2. 用於網絡傳送對象
  3. 用於遠程調用傳輸對象

3 準備序列化對象

準備了兩個類, 教師類和學生類, 其中一個學生只有一個教師
這裏省略了構造方法和setter, getter方法git

Teacher.javagithub

public class Teacher {
    private String name;
    private Integer age;
}

Student.javashell

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-databindjson

compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.11.2'

5 序列化與格式化輸出

5.1 流程

  1. 首先須要有一個待序列化對象, 本例中的student對象
  2. 建立一個對象映射器, jackson包下的ObjectMapper
  3. 調用序列化函數, 本例中的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 分析

  1. 示例中調用了方法writerWithDefaultPrettyPrinter, 美化了json的格式
  2. 不然將打印[{"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時間戮, 可讀性很是差app

現修改成yyyy-MM-dd HH:mm:ss
並設置時區爲東八區maven

示例代碼

@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方法

  1. 修改返回值類型爲int
  2. 調用父類的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呢?

  1. 在Student的teacher屬性上添加註解JsonUnwrapped, 意爲不包裹
  2. 在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, 獲得理論學齡, 如何操做呢?

  1. 建立自定義年齡序列化器AgeSerializer, 繼承StdSerializer<>
    1. 建立AgeSerializer的構造方法
    2. 重寫serialize函數
  2. 利用註解修指定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 流程

  1. 首先須要有序列化好的數據, 能夠是string, byte[], 文件二進制等
  2. 建立一個對象映射器, jackson包下的ObjectMapper
  3. 調用反序列化函數, 本例中的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}}

分析

  1. 爲了便於打印對象數據, 重寫了Student和Teacher的toString方法
  2. 從數據中能夠看出, 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}}

分析

  1. readValue的第二個參數須要傳類型, 這裏推薦用數組, 不推薦用List, 具體緣由筆者目前也沒花時間去研究

10.4 自定義反序列化器

從10.2節及10.3的現象中能夠看出來, 僅僅自定義的序列化器會致使序列化的過程是正常的, 反序列化的過程仍然是默認邏輯, 有時候會致使意想不到的結果

遇到此場景, 能夠考慮自定義反序列化器

  1. 建立自定義反序列化器AgeDeserializer, 繼承StdDeserializer<>
  2. 重寫deserialize方法
  3. 在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
}

分析

  1. 當屬性爲默認值, 即零值時, 不序列化
  2. 常見的零值:
    1. int: 0
    2. bool: false,
    3. String: null

12 註解Transient

13 註解JsonIgnore

相關文章
相關標籤/搜索