我们日常理解的Jackson 是一个能够将java对象序列化为JSON字符串的第三方库,实际Jackson的作用和给日常开发带来的便利性远超过我们日常使用的范畴,其实际的作用和为日常开发带来的便利性,一直未被大家全面认识和利用,在此本文为大家整理出如何使用Jackson进行序列化的相关内容。
目录
基本用法
1. JSON
2. xml
3. MessagePack
容器对象
1. List
2. Map
复杂对象处理
特殊场景下的自定义序列化
1.忽略字段
2.引用同一个对象
3.未知字段的异常处理
4.继承和多态
5.循环引用
6.修改字段名称
7.格式化日期
8.配置构造方法
基本用法
下面我们以Student类为例来演示Jackson序列化为JSON、ⅩML和Message-Pack格式的基本用法。
public class Student {
String name;
int age;
double score;
public Student() {
super();
}
public Student(String name, int age, double score) {
super();
this.name = name;
this.age = age;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score
+ "]";
}
}
1. JSON
Jackson序列化的主要类是ObjectMapper,它是一个线程安全的类,可以初始化并配置一次,被多个线程共享,通过设置SerializationFeature.INDENT_OUTPUT的目的是格式化输出,以便于阅读。ObjectMapper的writeValueAsString方法就可以将对象序列化为字符串,序列化一个Student对象的基本代码为:
Student student = new Student("小五", 12, 88.9d);
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(student);
System.out.println(str);
输出为:
{
"name" : "小五",
"age" : 12,
"score" : 88.9
}
另外,如下所示,ObjectMapper可以输出字节数组,写出到文件、OutputStream、Writer等。
public byte[] writeValueAsBytes(Object value)
public void writeValue(OutputStream out, Object value)
public void writeValue(Writer w, Object value)
public void writeValue(File resultFile, Object value)
比如,输出到文件"student.json",代码为:
mapper.writeValue(new File("student.json"), student);
ObjectMapper与Java标准序列化机制一样,使用反射机制实现反序列化,默认情况下,它会保存所有声明为public的字段,或者有public getter方法的字段。反序列化的代码如下所示:
ObjectMapper mapper = new ObjectMapper();
//使用readValue方法反序列化,有两个参数:
//一个是输入源,这里是文件student.json,另一个是反序列化后的对象类型,这里是Student.class
Student s = mapper.readValue(new File("student.json"), Student.class);
System.out.println(s.toString());
输出的结果为:
Student [name=小五, age=12, score=88.9]
另外除了接受文件,如下所示,还可以是字节数组、字符串、Input-Stream、Reader等,在反序列化时,默认情况下,Jackson假定对象类型有一个无参的构造方法,它会先调用该构造方法创建对象,然后解析输入源进行反序列化。如下所示。
public <T> T readValue(InputStream src, Class<T> valueType)
public <T> T readValue(Reader src, Class<T> valueType)
public <T> T readValue(String content, Class<T> valueType)
public <T> T readValue(byte[] src, Class<T> valueType)
2. xml
只需要将上面的ObjectMapper为Ⅹml-Mapper就可实现xml格式的序列化,而ⅩmlMapper是ObjectMapepr的子类。序列化代码如下:
Student student = new Student("小五", 12, 88.9d);
ObjectMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(student);
mapper.writeValue(new File("student.xml"), student);
System.out.println(str);
输出的xml文件内容为:
<Student>
<name>小五</name>
<age>12</age>
<score>88.9</score>
</Student>
反序列化成Students对象的代码如下:
ObjectMapper mapper = new XmlMapper();
Student s = mapper.readValue(new File("student.xml"), Student.class);
System.out.println(s.toString());
3. MessagePack
与上面的xml类似,同样使用ObjectMapper类可实现MessagePack格式序列化,序列化代码为:
Student student = new Student("小五", 12, 88.9d);
//需要传递一个Mess-agePackFactory对象
ObjectMapper mapper = new ObjectMapper(new MessagePackFactory());
byte[] bytes = mapper.writeValueAsBytes(student);
//MessagePack是二进制格式,因此不能写出为String
//可以写出为文件、OutpuStream或字节数组。
mapper.writeValue(new File("student.bson"), student);
反序列化代码如下:
ObjectMapper mapper = new ObjectMapper(new MessagePackFactory());
Student s = mapper.readValue(new File("student.bson"), Student.class);
System.out.println(s.toString());
容器对象
对于List和Map之类的容器对象,Jackson也同样可以自动处理的,但用法稍有不同,下面我们来一起了解和学习。
1. List
使用List容器序列化一个学生列表的代码为:
List<Student> students = Arrays.asList(new Student[] {
new Student("小五", 12, 88.9d), new Student("李四", 17, 67.5d) });
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(students);
mapper.writeValue(new File("students.json"), students);
System.out.println(str);
与前面章节中序列化一个学生对象的代码是类似的,输出为:
[ {
"name" : "小五",
"age" : 12,
"score" : 88.9
}, {
"name" : "李四",
"age" : 17,
"score" : 67.5
} ]
反序列化代码不同,要新建一个TypeReference匿名内部类对象来指定类型,代码如下所示:
ObjectMapper mapper = new ObjectMapper();
List<Student> list = mapper.readValue(new File("students.json"),
new TypeReference<List<Student>>() {});//要新建一个TypeReference匿名内部类对象来指定类型
System.out.println(list.toString());
2. Map
Map与List类似,序列化不需要特殊处理,反序列化同样需要通过TypeReference指定类型,下面通过一个ⅩML的例子来实现序列化一个学生Map,代码如下:
Map<String, Student> map = new HashMap<String, Student>();
map.put("xiaowu", new Student("小五", 12, 88.9d));
map.put("lisi", new Student("李四", 17, 67.5d));
ObjectMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(map);
mapper.writeValue(new File("students_map.xml"), map);
System.out.println(str);
输出结果为:
<HashMap>
<lisi>
<name>李四</name>
<age>17</age>
<score>67.5</score>
</lisi>
<xiaowu>
<name>小五</name>
<age>12</age>
<score>88.9</score>
</zhangsan>
</HashMap>
反序列化代码:
ObjectMapper mapper = new XmlMapper();
Map<String, Student> map = mapper.readValue(new File("students_map.xml"),
new TypeReference<Map<String, Student>>() {});
System.out.println(map.toString());
复杂对象处理
对于如下复杂后的ComplexStudent 对象,Jackson同样可以处理自如。如下所示,将原来Student的分数改为一个Map,键为课程,ContactInfo表示联系信息,是一个单独的类。
public class ComplexStudent {
String name;
int age;
Map<String, Double> scores;
ContactInfo contactInfo;
public ComplexStudent() {
super();
}
public ComplexStudent(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Map<String, Double> getScores() {
return scores;
}
public void setScores(Map<String, Double> scores) {
this.scores = scores;
}
public ContactInfo getContactInfo() {
return contactInfo;
}
public void setContactInfo(ContactInfo contactInfo) {
this.contactInfo = contactInfo;
}
}
ContactInfo类如下。
public class ContactInfo {
String phone;
String address;
String email;
public ContactInfo(String phone, String address, String email) {
super();
this.phone = phone;
this.address = address;
this.email = email;
}
public ContactInfo() {
super();
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
构建一个ComplexStudent对象,代码为:
ComplexStudent student = new ComplexStudent("张三", 18);
Map<String, Double> scoreMap = new HashMap<>();
scoreMap.put("语文", 89d);
scoreMap.put("数学", 83d);
student.setScores(scoreMap);
ContactInfo contactInfo = new ContactInfo();
contactInfo.setPhone("18500308990");
contactInfo.setEmail("zhangsan@sina.com");
contactInfo.setAddress("中关村");
student.setContactInfo(contactInfo);
JSON和xml格式的序列化代码如下:
package shuo.laoma.file.c63;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
public class ComplexStudentDemo {
public static void printAsJson() throws IOException{
ComplexStudent student = new ComplexStudent("张三", 18);
Map<String, Double> scoreMap = new HashMap<>();
scoreMap.put("语文", 89d);
scoreMap.put("数学", 83d);
student.setScores(scoreMap);
ContactInfo contactInfo = new ContactInfo();
contactInfo.setPhone("18500308990");
contactInfo.setEmail("zhangsan@sina.com");
contactInfo.setAddress("中关村");
student.setContactInfo(contactInfo);
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
System.out.println(mapper.writeValueAsString(student));
}
public static void printAsXml() throws IOException{
ComplexStudent student = new ComplexStudent("张三", 18);
Map<String, Double> scoreMap = new HashMap<>();
scoreMap.put("语文", 89d);
scoreMap.put("数学", 83d);
student.setScores(scoreMap);
ContactInfo contactInfo = new ContactInfo();
contactInfo.setPhone("18500308990");
contactInfo.setEmail("zhangsan@sina.com");
contactInfo.setAddress("中关村");
student.setContactInfo(contactInfo);
ObjectMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
System.out.println(mapper.writeValueAsString(student));
}
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
printAsJson();
printAsXml();
}
}
Json输出为:
{
"name" : "张三",
"age" : 18,
"scores" : {
"语文" : 89.0,
"数学" : 83.0
},
"contactInfo" : {
"phone" : "18500308990",
"address" : "中关村",
"email" : "zhangsan@sina.com"
}
}
xml输出为:
<ComplexStudent>
<name>张三</name>
<age>18</age>
<scores>
<语文>89.0</语文>
<数学>83.0</数学>
</scores>
<contactInfo>
<phone>18500308990</phone>
<address>中关村</address>
<email>zhangsan@sina.com</email>
</contactInfo>
</ComplexStudent>
反序列化的代码也不需要特殊处理,指定类型为ComplexStudent.class即可。
特殊场景下的自定义序列化
1.忽略字段
在使用Jackson进行序列化操作时,有时候,部分对象或者字段我么实际使用时不需要进行序列化,就需要对这部分属性在进行序列化时自动忽略掉,比如在Java标准序列化中,如果字段标记为了transient,就会在序列化中被忽略,在Jackson中,可以使用以下两个注解之一。
- @JsonIgnore:用于字段、getter或setter方法等任一地方。
- @JsonIgnoreProperties:用于类声明,可指定忽略一个或多个字段。
比如,上面的Student类,忽略分数字段,修饰getter方法可以为:
//忽略分数字段
@JsonIgnore
double score;
//修饰getter方法
@JsonIgnore
public double getScore() {
return score;
}
加了以上任一标记后,序列化后的结果中将不再包含score字段,在反序列化时,即使输入源中包含score字段的内容,也不会给score字段赋值,也可以修饰Student类:
@JsonIgnoreProperties("score")
public class Student {
...
2.引用同一个对象
对于两个类中引用同一个对象时,在进行序列化和反序列化时,如果特殊处理的话,反序列化出的结果必然将原来引用的同一对象反序列化成不同的两个对象,例如下面的伪代码可进行验证。
有两个类Common和A, A中有两个Common对象,为便于演示,我们将所有属性定义为了public,它们的类定义如下:
static class Common {
public String name;
}
static class A {
public Common first;
public Common second;
}
其中对象a的属性first和second都指向都一个c对象,不加额外配置。
Common c = new Common();
c.name= "common";
A a = new A();
a.first = a.second = c;
对a序列化的代码为:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(a);
System.out.println(str);
输出后为:
{
"first" : {
"name" : "abc"
},
"second" : {
"name" : "abc"
}
}
再进行反序列化后,如下所示,first和second将指向不同的对象。运行后,最终结果运行到第二步“------反序列化后对象不一致-----”。
A a2 = mapper.readValue(str, A.class);
if(a2.first == a2.second){
System.out.println("------反序列化后对象一致------");
}else{
System.out.println("------反序列化后对象不一致-----");
}
Jackson可以使用注解@JsonIdentityInfo,对Common类做注解,来保持这种对同一个对象的引用关系,如下所示:
//property="id"表示在序列化输出中新增一个属性"id"以表示对象的唯一标示
//generator表示对象唯一ID的产生方法,这里是使用整数顺序数产生器IntSequenceGenerator。
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class,property="id")
static class Common {
public String name;
}
最终序列化后的结果如下,其中"first"中加了一个属性"id",而"second"的值只是1,表示引用第一个对象,这个格式反序列化后,first和second会指向同一个对象。。
{
"first" : {
"id" : 1,
"name" : "common"
},
"second" : 1
}
3.未知字段的异常处理
在Java标准序列化中,反序列化时,对于未知字段会自动忽略,
在Jackson中,反序列化时,对于未知字段会默认情况下会抛出异常。还是以Student类为例,如果student.json文件的内容为:
{
"name" : "张三",
"age" : 18,
"score": 333,
"other": "其他信息"//student对象中没有该字段
}
其中,other属性是Student类没有的,如果使用标准的反序列化代码。
ObjectMapper mapper = new ObjectMapper();
Student s = mapper.readValue(new File("student.json"), Student.class);
则会抛出异常com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "other" ...。
对于以上未知字段的反序列化异常,Jackson提供了两种方式可以忽略未知字段的反序列化,避免异常出现。
方法一,配置ObjectMapper。
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Student s = mapper.readValue(new File("student.json"), Student.class);
方法2:配置在具体的类上
@JsonIgnoreProperties(ignoreUnknown=true)
public class Student {
//...
}
4.继承和多态
Jackson也不能自动处理多态的情况。我们看个例子,有4个类,定义如下,我们忽略了构造方法和getter/setter方法:
static class Shape {
}
static class Circle extends Shape {
private int r;
}
static class Square extends Shape {
private int l;
}
static class ShapeManager {
private List<Shape> shapes;
}
ShapeManager中的Shape列表中的对象可能是Circle,也可能是Square。比如,有一个ShapeManager对象,如下所示:
ShapeManager sm = new ShapeManager();
List<Shape> shapes = new ArrayList<Shape>();
shapes.add(new Circle(10));
shapes.add(new Square(5));
sm.setShapes(shapes);
序列化后结果为:
{
"shapes" : [ {
"r" : 10
}, {
"l" : 5
} ]
}
以上结果由于输出中没有类型信息,反序列化时,Jackson不知道具体的Shape类型是什么,就会抛出异常。解决方法是在输出中包含类型信息,在基类Shape前使用如下注解,反序列化时就可以正确解析了:
@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Circle.class, name = "circle"),
@JsonSubTypes.Type(value = Square.class, name = "square") })
static class Shape {
}
输出结果为:
{
"shapes" : [ {
"type" : "circle",
"r" : 10
}, {
"type" : "square",
"l" : 5
} ]
}
5.循环引用
有两个类Parent和Child,它们相互引用,形成循环引用的场景:
static class Parent {
public String name;
public Child child;
}
static class Child {
public String name;
public Parent parent;
}
其中有一个对象如下。如果序列化parent这个对象,Jackson会进入无限循环,最终抛出异常
Parent parent = new Parent();
parent.name = "老马";
Child child = new Child();
child.name = "小马";
parent.child = child;
child.parent = parent;
解决死循环异常问题,可以分别标记Parent类中的child和Child类中的parent字段,将其中一个标记为主引用,而另一个标记为反向引用,主引用使用@JsonManagedReference,反向引用使用@JsonBackReference。
static class Parent {
public String name;
@JsonManagedReference
public Child child;
}
static class Child {
public String name;
@JsonBackReference
public Parent parent;
}
序列化代码如下。
ObjectMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(parent);
System.out.println(str);
生成的xml结果如下。
<Parent>
<name>老马</name>
<child>
<name>小马</name>
</child>
</Parent>
在以上输出中,各项显示正常,反向引用没有出现。在反序列化时,Jackson会自动设置Child对象中的parent字段的值。代码如下。
Parent parent2 = mapper.readValue(str, Parent.class);
System.out.println(parent2.child.parent.name);
输出为:老马。说明标记为反向引用的字段的值也被正确设置了。
6.修改字段名称
对于ⅩML/JSON格式,有时,我们希望修改输出的名称,比如对Student类,我们希望输出的字段名变为对应的中文,可以使用@JsonProperty进行注解,如下所示:
public class Student {
@JsonProperty("名称")
String name;
@JsonProperty("年龄")
int age;
@JsonProperty("分数")
double score;
//……
}
@JsonProperty进行注解后,输出结果如下。
{
"名称" : "张三",
"年龄" : 18,
"分数" : 80.9
}
对于ⅩML格式,一个常用的修改是根元素的名称。默认情况下,它是对象的类名,比如对Student对象,它是"Student",如果希望修改,比如改为小写"student",可以使用@JsonRootName修饰整个类,如下所示:
@JsonRootName("student")
public class Student {
//
......
}
7.格式化日期
默认情况下,日期的序列化格式为一个长整数,比如:
static class MyDate {
public Date date = new Date();
}
//序列化代码:
MyDate date = new MyDate();
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(System.out, date);
以上的输出结果为:{"date":1482758152509},对于这个不可读的格式,使用@JsonFormat注解可以有效解决,如下所示。
static class MyDate {
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
public Date date = new Date();
}
注解后的输出为{"date":"2016-12-26 21:26:18"}。
8.配置构造方法
如下所示,Student类如果没有定义默认构造方法,则反序列化时会抛异常,提示找不到合适的构造方法:
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
针对以上异常,可以使用@JsonCreator和@Json-Property标记该构造方法解决,如下所示:
@JsonCreator
public Student(
@JsonProperty("name") String name,
@JsonProperty("age") int age,
@JsonProperty("score") double score) {
this.name = name;
this.age = age;
this.score = score;
}