Jackson 是用来序列化和反序列化 json 的 Java 的开源框架。Spring MVC 的默认 json 解析器便是 Jackson。与其他 Java 的 json 的框架 Gson 等相比, Jackson 解析大的 json 文件速度比较快;Jackson 运行时占用内存比较低,性能比较好;Jackson 有灵活的 API,可以很容易进行扩展和定制。
Jackson 的 1.x 版本的包名是 org.codehaus.jackson ,当升级到 2.x 版本时,包名变为 com.fasterxml.jackson ackson 的核心模块由三部分组成。
jackson-core,核心包,提供基于"流模式"解析的相关 API,它包括 JsonPaser 和 JsonGenerator。Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 json。
jackson-annotations,注解包,提供标准注解功能;
jackson-databind ,数据绑定包, 提供基于"对象绑定" 解析的相关 API ( ObjectMapper ) 和"树模型" 解析的相关 API (JsonNode);基于"对象绑定" 解析的 API 和"树模型"解析的 API 依赖基于"流模式"解析的 API。
若想在 Java 代码中使用 Jackson 的核心模块的 jar 包 ,需要在 pom.xml 中添加如下信息。
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.10</version> </dependency>说明 jackson-databind 依赖 jackson-core 和 jackson-annotations,当添加 jackson-databind 之后, jackson-core 和 jackson-annotations 也随之添加到 Java 项目工程中。在添加相关依赖包之后,就可以使用 Jackson。
简单使用
/** * 测试序列化 * @throws JsonProcessingException */ @Test public void test01() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); Student student = build(); String json = objectMapper.writeValueAsString(student); System.out.println(json); } private Student build() { return Student.builder() .age(18) .date(new Date()) .height(100) .course(Arrays.asList("Math","English","Chinese")) .teacher(Teacher.builder().name("张老师").build()) .name("Jackson").build(); }输出
{"name":"Jackson","age":18,"date":1603263016919,"height":100,"course":["Math","English","Chinese"],"teacher":{"name":"张老师"}}格式化json
@Test public void test02() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); Student student = build(); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(student); System.out.println(json); }输出
{ "name" : "Jackson", "age" : 18, "date" : 1603263150974, "height" : 100, "course" : [ "Math", "English", "Chinese" ], "teacher" : { "name" : "张老师" } }ObjectMapper 通过 writeValue 系列方法将 java 对象序列化为 json,并将 json 存储成不同的格式,·String·,ByteArray,Writer, File,OutStream 和 DataOutput。
ObjectMapper 通过readValue系列方法从不同的数据源像 String , Byte Array, Reader,File,URL, InputStream将 json 反序列化为 java 对象。
在调用 writeValue 或调用 readValue 方法之前,往往需要设置 ObjectMapper 的相关配置信息。这些配置信息应用 java 对象的所有属性上。示例如下:
//在反序列化时忽略在 json 中存在但 Java 对象不存在的属性 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); //在序列化时日期格式默认为 yyyy-MM-dd'T'HH:mm:ss.SSSZ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); //在序列化时自定义时间日期格式 mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); //在序列化时忽略值为 null 的属性 mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //在序列化时忽略值为默认值的属性 mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);更多配置信息可以查看 Jackson 的 DeserializationFeature,SerializationFeature 和 Include。
测试刚刚上面的几个配置
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
不忽略,默认也是不忽略 public ObjectMapper objectMapper = new ObjectMapper(); @Test public void test01() throws IOException { // 在反序列化时不忽略在 json 中存在但 Java 对象不存在的属性 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,true); //Teacher 中没有age属性 String json = "{\n" + " \"name\": \"张老师\",\n" + " \"age\": 40\n" + "}"; Teacher teacher = objectMapper.readValue(json, Teacher.class); System.out.println(teacher); }默认就是true,不会忽略在 json 中存在但 Java 对象不存在的属性,这时候在反序列化的时候就会报错
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "age" (class study.wyy.json.study.jackson.model.Teacher), not marked as ignorable (one known property: "name"]) at [Source: (String)"{ "name": "张老师", "age": 40 }"; line: 3, column: 12] (through reference chain: study.wyy.json.study.jackson.model.Teacher["age"]) at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61) at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:823) at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1153) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1589) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1567) at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:294) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4014) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3005) at study.wyy.json.study.jackson.demo.ConfigObjectMapper.test01(ConfigObjectMapper.java:29) 设置为忽略 @Test public void test02() throws IOException { // 在反序列化时忽略在 json 中存在但 Java 对象不存在的属性 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false); //language=JSON String json = "{\n" + " \"name\": \"张老师\",\n" + " \"age\": 40\n" + "}"; Teacher teacher = objectMapper.readValue(json, Teacher.class); System.out.println(teacher); } Teacher(name=张老师)SerializationFeature.WRITE_DATES_AS_TIMESTAMPS 在序列化时日期是否以时间戳的形式,默认是true,可参考快速入门例子
@Test public void test03() throws IOException { objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); Student student = build(); String value = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(student); System.out.println(value); } { "name" : "Jackson", "age" : 18, "date" : "2020-10-21T08:11:01.584+0000", "height" : 100, "course" : [ "Math", "English", "Chinese" ], "teacher" : { "name" : "张老师" } }在序列化时自定义时间日期格式 在序列化时日期是否以时间戳的形式为false时候,默认是2020-10-21T08:11:01.584+0000
如何自定义呢
/** * 自定义时间序列化格式 * @throws IOException */ @Test public void test04() throws IOException { objectMapper.setDateFormat(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")); Student student = build(); String value = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(student); System.out.println(value); } { "name" : "Jackson", "age" : 18, "date" : "2020/10/21 16:26:14", "height" : 100, "course" : [ "Math", "English", "Chinese" ], "teacher" : { "name" : "张老师" } }在序列化时忽略值为 null 的属性
@Test public void test05() throws IOException { objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); Student student = build(); student.setAge(null); String value = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(student); System.out.println(value); } { "name" : "Jackson", "date" : 1603269862759, "height" : 100, "course" : [ "Math", "English", "Chinese" ], "teacher" : { "name" : "张老师" } }在序列化时忽略值为默认值的属性
先不忽略 @Test public void test06() throws IOException { //objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); Student student = Student.builder() .name("jackson") .date(new Date()) .age(18) .build(); String value = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(student); System.out.println(value); } { "name" : "jackson", "age" : 18, "date" : 1603270077300, "height" : 0, "course" : null, "teacher" : null } 设置忽略 @Test public void test06() throws IOException { objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); Student student = Student.builder() .name("jackson") .date(new Date()) .age(18) .build(); String value = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(student); System.out.println(value); } { "name" : "jackson", "age" : 18, "date" : 1603270133718 }那些没有设置的属性,这里就没有序列化。
准备一个模型
@Data @Builder @ToString @NoArgsConstructor @AllArgsConstructor public class StudentWithAnnotations { private String name; private Integer age; private Date date; private int height; @JsonIgnore private List<String> course; // 班主任, private Teacher teacher; }可用于字段、getter/setter、构造函数参数上,作用相同,都会对相应的字段产生影响。使相应字段不参与序列化和反序列化。
course设置了@JsonIgnore注解,序列化的时候会忽略这个字段 public ObjectMapper objectMapper = new ObjectMapper(); /** * 测试忽略, * @throws IOException */ @Test public void testJsonIgnore() throws IOException { StudentWithAnnotations student = buildStudentWithAnnotations(); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(student); System.out.println(json); } public StudentWithAnnotations buildStudentWithAnnotations() { return StudentWithAnnotations.builder() .age(18) .date(new Date()) .height(100) .course(Arrays.asList("Math","English","Chinese")) .teacher(Teacher.builder().name("张老师").build()) .name("Jackson").build(); }输出
{ "name" : "Jackson", "age" : 18, "date" : 1603271223291, "height" : 100, "teacher" : { "name" : "张老师" } }那发序列化的时候呢
/*** * 测试反序列化 * @return */ @Test public void testJsonIgnore2() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); Student student = build(); // 先序列化 String json = objectMapper.writeValueAsString(student); System.out.println(); // 反序列化 StudentWithAnnotations studentFromJson = objectMapper.readValue(json, StudentWithAnnotations.class); System.out.println(studentFromJson); }输出:course也是null,也会忽略
StudentWithAnnotations(name=Jackson, age=18, date=Wed Oct 21 17:10:18 CST 2020, height=100, course=null, teacher=Teacher(name=张老师))用于属性,把属性的名称序列化时转换为另外一个名称。
@JsonProperty("firstName") private String name; @Test public void testJsonProperty() throws IOException { StudentWithAnnotations student = buildStudentWithAnnotations(); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(student); System.out.println(json); }输出: name属性在序列化json的时候,变成了firstName
{ "age" : 18, "date" : 1603272038044, "height" : 100, "teacher" : { "name" : "张老师" }, "firstName" : "Jackson" }反序列化
/*** * 测试反序列化 * @return */ @Test public void testJsonProperty2() throws IOException { StudentWithAnnotations student = buildStudentWithAnnotations(); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(student); // 反序列化 StudentWithAnnotations studentFromJson = objectMapper.readValue(json, StudentWithAnnotations.class); System.out.println(studentFromJson); }输出:同一json字符串中的firstName也是可以映射到name属性的
StudentWithAnnotations(name=Jackson, age=18, date=Wed Oct 21 17:22:55 CST 2020, height=100, course=null, teacher=Teacher(name=张老师))用于属性或者方法,把属性的格式序列化时转换成指定的格式。
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm") private Date date; @Test public void testJsonFormat() throws IOException { StudentWithAnnotations student = buildStudentWithAnnotations(); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(student); System.out.println(json); }输出
{ "age" : 18, "date" : "2020-10-21 17:33", "height" : 100, "teacher" : { "name" : "张老师" }, "firstName" : "Jackson" }不同类型的日期类型,JackSon 的处理方式也不同。
对于日期类型为java.util.Calendar, java.util.GregorianCalendar,java.sql.Date, java.util.Date, java.sql.Timestamp,若不指定格式,在 json 文件中将序列化为 long 类型的数据。显然这种默认格式,可读性差,转换格式是必要的。
JackSon 有很多方式转换日期格式。
注解方式,使用 @JsonFormat 注解指定日期格式。
ObjectMapper 方式,调用 ObjectMapper 的方法 setDateFormat,将序列化为指定格式的 string 类型的数据。
上面已经介绍过啦
对于日期类型为java.time.LocalDate, java.time.LocalDateTime,还需要添加代码 ,同时添加相应的依赖 jar 包。
ObjectMapper mapper = new ObjectMapper() .registerModule(new JavaTimeModule())演示
给Teacher增加两个属性
@Data @ToString @Builder @AllArgsConstructor @NoArgsConstructor public class Teacher { private String name; private LocalDate localDate; private LocalDateTime localDateTime; } @Test public void test01() throws JsonProcessingException { Teacher teacher = new Teacher(); teacher.setName("张老师"); teacher.setLocalDate(LocalDate.now()); teacher.setLocalDateTime(LocalDateTime.now()); objectMapper.registerModule(new JavaTimeModule()); String value = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(teacher); System.out.println(value); }输出:
{ "name" : "张老师", "localDate" : [ 2020, 10, 21 ], "localDateTime" : [ 2020, 10, 21, 17, 55, 53, 375000000 ] }这个时间格式不太友好,可以直接使用我们的注解去进行格式化
@Data @ToString @Builder @AllArgsConstructor @NoArgsConstructor public class Teacher { private String name; @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd") private LocalDate localDate; @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime localDateTime; }再次运行结果
{ "name" : "张老师", "localDate" : "2020-10-21", "localDateTime" : "2020-10-21 17:58:22" }对于日期类型为 org.joda.time.DateTime,还需要添加代码 mapper.registerModule(new JodaModule()),同时添加相应的依赖 jar 包
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-joda</artifactId> <version>2.9.10</version> </dependency>Jackson 对泛型反序列化也提供很好的支持。
对于 List 类型 ,可以调用 constructCollectionType 方法来序列化,也可以构造 TypeReference 来序列化。
演示
@Test public void test() throws IOException { ObjectMapper mapper = new ObjectMapper(); // 造数据 List<Student> list = new ArrayList<>(); for (int i = 0; i < 3; i++) { Student student = new Student(); student.setName("Tom"); student.setAge(new Random().nextInt(100)); student.setDate(new Date()); list.add(student); } System.out.println("序列化"); String jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(list); System.out.println(jsonInString); System.out.println("反序列化:使用 javaType"); CollectionType javaType = mapper.getTypeFactory().constructCollectionType(List.class, Student.class); List<Student> personList = mapper.readValue(jsonInString, javaType); System.out.println(personList); System.out.println("反序列化:使用 TypeReference"); List<Student> personList2 = mapper.readValue(jsonInString, new TypeReference<List<Student>>() { }); System.out.println(personList2); } 序列化 [ { "name" : "Tom", "age" : 40, "date" : 1603285233310, "height" : 0, "course" : null, "teacher" : null }, { "name" : "Tom", "age" : 51, "date" : 1603285233310, "height" : 0, "course" : null, "teacher" : null }, { "name" : "Tom", "age" : 87, "date" : 1603285233310, "height" : 0, "course" : null, "teacher" : null } ] 反序列化:使用 javaType [Student(name=Tom, age=40, date=Wed Oct 21 21:00:33 CST 2020, height=0, course=null, teacher=null), Student(name=Tom, age=51, date=Wed Oct 21 21:00:33 CST 2020, height=0, course=null, teacher=null), Student(name=Tom, age=87, date=Wed Oct 21 21:00:33 CST 2020, height=0, course=null, teacher=null)] 反序列化:使用 TypeReference [Student(name=Tom, age=40, date=Wed Oct 21 21:00:33 CST 2020, height=0, course=null, teacher=null), Student(name=Tom, age=51, date=Wed Oct 21 21:00:33 CST 2020, height=0, course=null, teacher=null), Student(name=Tom, age=87, date=Wed Oct 21 21:00:33 CST 2020, height=0, course=null, teacher=null)]对于 map 类型, 与 List 的实现方式相似。
public void test6() throws IOException { ObjectMapper mapper = new ObjectMapper(); //第二参数是 map 的 key 的类型,第三参数是 map 的 value 的类型 MapType javaType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Student.class); // 造数据 Map<String, Student> map = new HashMap<>(); for (int i = 0; i < 3; i++) { Student student = new Student(); student.setName("Tom"); student.setAge(new Random().nextInt(100)); student.setDate(new Date()); map.put("key" + i, student); } System.out.println("序列化"); String jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(map); System.out.println(jsonInString); System.out.println("反序列化: 使用 javaType"); Map<String, Student> personMap = mapper.readValue(jsonInString, javaType); System.out.println(personMap); System.out.println("反序列化: 使用 TypeReference"); Map<String, Student> personMap2 = mapper.readValue(jsonInString, new TypeReference<Map<String, Student>>() { }); System.out.println(personMap2); } 序列化 { "key1" : { "name" : "Tom", "age" : 44, "date" : 1603285404538, "height" : 0, "course" : null, "teacher" : null }, "key2" : { "name" : "Tom", "age" : 44, "date" : 1603285404538, "height" : 0, "course" : null, "teacher" : null }, "key0" : { "name" : "Tom", "age" : 2, "date" : 1603285404538, "height" : 0, "course" : null, "teacher" : null } } 反序列化: 使用 javaType {key1=Student(name=Tom, age=44, date=Wed Oct 21 21:03:24 CST 2020, height=0, course=null, teacher=null), key2=Student(name=Tom, age=44, date=Wed Oct 21 21:03:24 CST 2020, height=0, course=null, teacher=null), key0=Student(name=Tom, age=2, date=Wed Oct 21 21:03:24 CST 2020, height=0, course=null, teacher=null)} 反序列化: 使用 TypeReference {key1=Student(name=Tom, age=44, date=Wed Oct 21 21:03:24 CST 2020, height=0, course=null, teacher=null), key2=Student(name=Tom, age=44, date=Wed Oct 21 21:03:24 CST 2020, height=0, course=null, teacher=null), key0=Student(name=Tom, age=2, date=Wed Oct 21 21:03:24 CST 2020, height=0, course=null, teacher=null)}JackSon 默认不是所有的属性都可以被序列化和反序列化。默认的属性可视化的规则如下:
若该属性修饰符是 public,该属性可序列化和反序列化。
若属性的修饰符不是 public,但是它的 getter 方法和 setter 方法是 public,该属性可序列化和反序列化。因为 getter 方法用于序列化, 而 setter 方法用于反序列化。
若属性只有 public 的 setter 方法,而无 public 的 getter 方 法,该属性只能用于反序列化。