jackson序列化与反序列化的应用实践

源码地址:

https://github.com/zhouweixin/serializable

1 相关概念

  1. 序列化: 把对象转换为字节序列的过程称为对象的序列化
  2. 反序列化: 把字节序列恢复为对象的过程称为对象的反序列化

2 序列化的作用

  1. 用于把内存中的对象状态保存到一个文件中或者数据库中
  2. 用于网络传送对象
  3. 用于远程调用传输对象

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 流程

  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时间戮, 可读性非常差

现修改为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方法

  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的第一层,

有时可能会更深的层次, 使用起来不太友好

如何用teacherName和teacherAge两个属性代替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}}

jackson序列化与反序列化的应用实践的更多相关文章

  1. Jackson序列化和反序列化Json数据完整示例

    Jackson序列化和反序列化Json数据 Web技术发展的今天,Json和XML已经成为了web数据的事实标准,然而这种格式化的数据手工解析又非常麻烦,软件工程界永远不缺少工具,每当有需求的时候就会 ...

  2. Jackson序列化和反序列化

    1,下载Jackson工具包(jackson-core-2.2.3.jar  jackson-annotations-2.2.3.jar  jackson-databind-2.2.3.jar ) j ...

  3. Jackson 序列化和反序列化

    博客地址:https://www.moonxy.com 一.前言 Jackson 功能很强大,既能满足简单的序列化和反序列化操作,也能实现复杂的.个性化的序列化和反序列化操作.到目前为止,Jackso ...

  4. MessagePack Java Jackson 序列化和反序列化 POJO 为 MessagePack 的数组类型用来与 msgpack-java:0.6 保持兼容性

    在 msgpack-java 0.6 或者早期的版本中,POJO 在 MessagePack 中被序列化和反序列化为数组变量. 变量的顺序是基于 Java 类中变量的内部顺序了,这种本来是一种原生的序 ...

  5. Gson、jackson 序列化,反序列化(单个、集合)

    实体类: package com.nf.redisDemo1.entity; public class News { private long id; private String title; pr ...

  6. 第一章 JacksonUtil 序列化与反序列化属性总结

    1.json-lib与Jackson 关于json-lib与Jackson对比总结如下: 1).性能方面,Jackson的处理能力高出Json-lib10倍左右. 2).json-lib已经停止更新, ...

  7. SpringBoot系列——Jackson序列化

    前言 Spring Boot提供了与三个JSON映射库的集成: Gson Jackson JSON-B Jackson是首选的默认库. 官网介绍: https://docs.spring.io/spr ...

  8. Java序列化与反序列化(实践)

    Java序列化与反序列化(实践) 基本概念:序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数据. 昨天在一本书上 ...

  9. Jackson对泛型的序列化和反序列化方法汇总

    说明:Jackson对于简单泛型是可以正常操作的,但是如果对于太过于复杂的泛型类有时会不成功.目前还在找着更合适的Json库.不过这一点在dotnet原生方案JavaScriptSerializer可 ...

随机推荐

  1. C#LeetCode刷题之#859-亲密字符串​​​​​​​​​​​​​​(Buddy Strings)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3973 访问. 给定两个由小写字母构成的字符串 A 和 B ,只要 ...

  2. [源码解析] Flink UDAF 背后做了什么

    [源码解析] Flink UDAF 背后做了什么 目录 [源码解析] Flink UDAF 背后做了什么 0x00 摘要 0x01 概念 1.1 概念 1.2 疑问 1.3 UDAF示例代码 0x02 ...

  3. 浅谈:C#中的非泛型集合

    1.首先:ArrayList:非泛型集合 List:泛型集合 集合跟数组比较我们更容易理解.数组:1,长度固定2,数据类型预先声明 集合:1,长度可变2,数据类型预先声明的为泛型集合,数据类型不限定为 ...

  4. Linux内核之 基本概念

    一直想写写Linux内核的文章,特别是进程这方面的,说实话,不好写,也不太敢写:)直到遇到了一本好书.<Linux内核设计与实现>,原书<Linux Kernel Developme ...

  5. 在Linux使用虚拟环境

    定义 “虚拟环境”,是python解释器的一个私有副本.在这个环境中,你可以安装私有包,而且不会影响系统中安装的全局python解释器. 作用 为每个程序单独创建虚拟环境时,可以保证程序只能访问虚拟环 ...

  6. jQuery源码分析系列(二)Sizzle选择器引擎-上

    前言 我们继续从init()方法中的find()方法往下看, jQuery.find = Sizzle; ... find: function (selector) { /** ... */ ret ...

  7. Java面试题(异常篇)

    异常 74.throw 和 throws 的区别? throws是用来声明一个方法可能抛出的所有异常信息,throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理.而throw则是指 ...

  8. js 判断 数组和对象

    提方案的时候顺便会引申一下该方法的使用. 一,instanceOf:(可以判断) instanceOf运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性. 也可以这样说 ...

  9. centos6.8上安装部署 jhipster-registry

    必备环境:jdk8,git,maven 1.安装nodejs #由于采用编译的方式很容易出现一些意外的惊喜,所以我们这儿直接用yum命令安装 #1.查看nodejs版本(命令中不要加 -y 如果版本不 ...

  10. 【Azure DevOps系列】Azure DevOps生成代码覆盖率

    前言 在做单元测试时,代码覆盖率可以作为我们衡量代码质量的一个指标,本章我们将使用Azure DevOps帮助我们生成代码覆盖率的结果.Azure DevOps构建管道还是具有代码覆盖率选项的,在Vi ...