简介

Jackson具有比较高的序列化和反序列化效率,据测试,无论是哪种形式的转换,Jackson > Gson > Json-lib,而且Jackson的处理能力甚至高出Json-lib近10倍左右,且正确性也十分高。相比之下,Json-lib似乎已经停止更新,最新的版本也是基于JDK15,而Jackson的社区则较为活跃。
github地址:https://github.com/FasterXML/jackson

Maven依赖

  1. <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
  2. <dependency>
  3. <groupId>com.fasterxml.jackson.core</groupId>
  4. <artifactId>jackson-databind</artifactId>
  5. <version>2.9.3</version>
  6. </dependency>
  7. <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
  8. <dependency>
  9. <groupId>com.fasterxml.jackson.core</groupId>
  10. <artifactId>jackson-core</artifactId>
  11. <version>2.9.3</version>
  12. </dependency>
  13. <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
  14. <dependency>
  15. <groupId>com.fasterxml.jackson.core</groupId>
  16. <artifactId>jackson-annotations</artifactId>
  17. <version>2.9.3</version>
  18. </dependency>

Jackson使用

我们使用Jackson无非是为了序列化和反序列化,Jackson通常的序列化对象为ObjectMapper,我们可以通过配置该对象的属性来达到定制化序列化的效果,但是通常情况下,只需要简单的序列化即可。

1. 快速使用

用Lombok设置一个简单的Java类。

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class Friend {
  5. private String nickname;
  6. private int age;
  7. }

然后就可以处理JSON数据了。首先需要一个ObjectMapper对象,序列化和反序列化都需要它,然后可以使用它来进行javabean与字符串,byte数组和File文件之间的转换。

  1. ObjectMapper mapper = new ObjectMapper();
  2. Friend friend = new Friend("yitian", 25);
  3. // javabean 转json字符串
  4. String text = mapper.writeValueAsString(friend);
  5. // javabean 转json文件
  6. mapper.writeValue(new File("friend.json"), friend);
  7. // javabean 转json字节数组
  8. byte[] bytes = mapper.writeValueAsBytes(friend);
  9. System.out.println(text);
  10. // 字符串 转javabean
  11. Friend newFriend = mapper.readValue(text, Friend.class);
  12. // byte数组 转javabean
  13. newFriend = mapper.readValue(bytes, Friend.class);
  14. // File文件 转javabean
  15. newFriend = mapper.readValue(new File("friend.json"), Friend.class);
  16. System.out.println(newFriend);

2. 集合的映射

除了使用Java类进行映射之外,我们还可以直接使用Map和List等Java集合组织JSON数据,在需要的时候可以使用readTree()方法直接读取JSON中的某个属性值。需要注意的是从JSON转换为Map对象的时候,由于Java的类型擦除,所以类型需要我们手动用new TypeReference给出。

  1. ObjectMapper mapper = new ObjectMapper();
  2. Map<String, Object> map = new HashMap<>();
  3. map.put("age", 25);
  4. map.put("name", "yitian");
  5. map.put("interests", new String[]{"pc games", "music"});
  6. // map 转json字符串
  7. String text = mapper.writeValueAsString(map);
  8. System.out.println(text);
  9. // 字符串 转map
  10. Map<String, Object> map2 = mapper.readValue(text, new TypeReference<Map<String, Object>>() {
  11. });
  12. System.out.println(map2);
  13. // 提取json字符串中的属性值
  14. JsonNode root = mapper.readTree(text);
  15. String name = root.get("name").asText();
  16. int age = root.get("age").asInt();
  17. System.out.println("name:" + name + " age:" + age);

程序结果如下:

  1. {"name":"yitian","interests":["pc games","music"],"age":25}
  2. {name=yitian, interests=[pc games, music], age=25}
  3. name:yitian age:25

3. ObjectMapper的配置

Jackson预定义了一些配置,我们通过启用和禁用某些属性可以修改Jackson运行的某些行为。详细文档参考JacksonFeatures。这里有两种方式来对ObjectMapper进行配置,调用configure()方法可以接受配置名和要设置的值,Jackson 2.5版本新加的enable()和disable()方法则直接启用和禁用相应属性,我推荐使用后者。下面,我简单翻译一下Jackson README上列出的一些属性。

  • 开启美化输出

    1. mapper.enable(SerializationFeature.INDENT_OUTPUT);
  • 关闭空POJO类检测(开启会抛出异常)

    1. mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
  • 关闭日期输出为时间戳,配合@JsonFormat(pattern = “yyyy-MM-DD”)输出格式化字符串

    1. mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
  • 在遇到未知属性的时候不抛出异常

    1. mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
  • 强制JSON 空字符串(“”)转换为null对象值

    1. mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
  • 在JSON中允许C/C++ 样式的注释(非标准,默认禁用)

    1. mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
  • 允许没有引号的字段名(非标准)

    1. mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
  • 允许单引号(非标准)

    1. mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
  • 强制转义非ASCII字符

    1. mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
  • 将内容包裹为一个JSON属性,属性名由@JsonRootName注解指定

    1. mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);

    4. 用注解管理映射

    Jackson类库包含了很多注解,可以让我们快速建立Java类与JSON之间的关系。详细文档可以参考Jackson-Annotations。下面介绍一下常用的。

  • 属性命名

@JsonProperty注解指定一个属性用于JSON映射,默认情况下映射的JSON属性与注解的属性名称相同,不过可以使用该注解的value值修改JSON属性名,该注解还有一个index属性指定生成JSON属性的顺序,如果有必要的话。

  • 属性包含/排除

还有一些注解可以管理在映射JSON的时候包含或排除某些属性,下面介绍一下常用的几个。
@JsonIgnore注解用于排除某个属性,这样该属性就不会被Jackson序列化和反序列化。
@JsonIgnoreProperties注解是类注解。在序列化为JSON的时候,@JsonIgnoreProperties({“prop1”, “prop2”})会忽略pro1和pro2两个属性。在从JSON反序列化为Java类的时候,@JsonIgnoreProperties(ignoreUnknown=true)会忽略所有没有Getter和Setter的属性。该注解在Java类和JSON不完全匹配的时候很有用。
@JsonIgnoreType也是类注解,会排除所有指定类型的属性。

  • 序列化相关

@JsonPropertyOrder和@JsonProperty的index属性类似,指定属性序列化时的顺序。
@JsonRootName注解用于指定JSON根属性的名称。

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @JsonRootName("FriendDetail")
  5. @JsonIgnoreProperties({"uselessProp1", "uselessProp3"})
  6. public class FriendDetail {
  7. @JsonProperty("NickName")
  8. private String name;
  9. @JsonProperty("Age")
  10. private int age;
  11. private String uselessProp1;
  12. @JsonIgnore
  13. private int uselessProp2;
  14. private String uselessProp3;
  15. }

然后看看代码。需要注意的是,由于设置了排除的属性,所以生成的JSON和Java类并不是完全对应关系,所以禁用DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES是必要的。

  1. ObjectMapper mapper = new ObjectMapper();
  2. // mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
  3. mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
  4. FriendDetail fd = new FriendDetail("yitian", 25, "", 0, "");
  5. String text = mapper.writeValueAsString(fd);
  6. System.out.println(text);
  7. FriendDetail fd2 = mapper.readValue(text, FriendDetail.class);
  8. System.out.println(fd2);

运行结果如下。可以看到生成JSON的时候使用@JsonIgnoreProperties@JsonIgnore忽略了我们指定的uselessProp1、2、3,故在转换为Java对象的时候对应的属性为空。同时,我们使用了 @JsonProperty对成员变量进行了重命名

  1. {"NickName":"yitian","Age":25}
  2. FriendDetail(name=yitian, age=25, uselessProp1=null, uselessProp2=0, uselessProp3=null)

然后取消注释代码中的那行,也就是启用WRAP_ROOT_VALUE功能(将内容包裹为一个JSON属性,属性名由@JsonRootName注解指定),再运行一下程序,运行结果如下。可以看到生成的JSON结果发生了变化,而且由于JSON结果变化,所以Java类转换失败(所有字段值全为空)。WRAP_ROOT_VALUE这个功能在有些时候比较有用,因为有些JSON文件需要这种结构。

  1. {"FriendDetail":{"NickName":"yitian","Age":25}}
  2. FriendDetail(name=null, age=0, uselessProp1=null, uselessProp2=0, uselessProp3=null)

5. Java8日期时间类支持

Java8增加了一套全新的日期时间类,Jackson对此也有支持。这些支持是以Jackson模块形式提供的,所以首先就是注册这些模块。

  1. ObjectMapper mapper = new ObjectMapper()
  2. .registerModule(new JavaTimeModule())
  3. .registerModule(new ParameterNamesModule())
  4. .registerModule(new Jdk8Module());

我们也可以直接使用maven导入类库, 然后调用findAndRegisterModules()让Jackson自动搜索所有模块。

  1. <dependency>
  2. <groupId>com.fasterxml.jackson.datatype</groupId>
  3. <artifactId>jackson-datatype-jsr310</artifactId>
  4. <version>2.9.7</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.fasterxml.jackson.module</groupId>
  8. <artifactId>jackson-module-parameter-names</artifactId>
  9. <version>2.9.7</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>com.fasterxml.jackson.datatype</groupId>
  13. <artifactId>jackson-datatype-jdk8</artifactId>
  14. <version>2.9.7</version>
  15. </dependency>

我们新建一个带有LocalDate字段的Java类。

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @JsonRootName("Person")
  5. public class Person {
  6. @JsonProperty("Name")
  7. private String name;
  8. @JsonProperty("NickName")
  9. private String nickname;
  10. @JsonProperty("Age")
  11. private int age;
  12. @JsonProperty("IdentityCode")
  13. private String identityCode;
  14. @JsonProperty
  15. //@JsonFormat(pattern = "yyyy-MM-DD")
  16. private LocalDate birthday;
  17. }

然后使用Jackson来对日期进行转换:

  1. static void java8DateTime() throws IOException {
  2. Person p1 = new Person("yitian", "易天", 25, "10000", LocalDate.of(1994, 1, 1));
  3. ObjectMapper mapper = new ObjectMapper()
  4. .registerModule(new JavaTimeModule());
  5. //mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
  6. String text = mapper.writeValueAsString(p1);
  7. System.out.println(text);
  8. Person p2 = mapper.readValue(text, Person.class);
  9. System.out.println(p2);
  10. }

运行结果如下。可以看到,生成的JSON日期变成了1572420511790这样的时间戳形式,个别情况下不符合我们使用的要求。

  1. {"birthday":1572420511790,"Name":"yitian","NickName":"易天","Age":25,"IdentityCode":"10000"}
  2. Person(name=yitian, nickname=易天, age=25, identityCode=10000, birthday=1994-01-01)

取消两个类的注释部分,程序运行结果如下。这样一来就变成了我们一般使用的形式了。如果有格式需要的话,可以使用@JsonFormat(pattern = “yyyy-MM-DD”)注解格式化日期显示。

  1. {"birthday":"1994-01-01","Name":"yitian","NickName":"易天","Age":25,"IdentityCode":"10000"}
  2. Person(name=yitian, nickname=易天, age=25, identityCode=10000, birthday=1994-01-01)

6. XML映射

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @JsonRootName("Person")
  5. public class Person {
  6. @JsonProperty("Name")
  7. private String name;
  8. @JsonProperty("NickName")
  9. //@JacksonXmlText
  10. private String nickname;
  11. @JsonProperty("Age")
  12. private int age;
  13. @JsonProperty("IdentityCode")
  14. @JacksonXmlCData
  15. private String identityCode;
  16. @JsonProperty("Birthday")
  17. //@JacksonXmlProperty(isAttribute = true)
  18. @JsonFormat(pattern = "yyyy/MM/DD")
  19. private LocalDate birthday;
  20. }

下面是代码示例,基本上和JSON的API非常相似,XmlMapper实际上就是ObjectMapper的子类。

  1. Person p1 = new Person("yitian", "易天", 25, "10000", LocalDate.of(1994, 1, 1));
  2. XmlMapper mapper = new XmlMapper();
  3. mapper.findAndRegisterModules();
  4. mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
  5. mapper.enable(SerializationFeature.INDENT_OUTPUT);
  6. String text = mapper.writeValueAsString(p1);
  7. System.out.println(text);
  8. Person p2 = mapper.readValue(text, Person.class);
  9. System.out.println(p2);

运行结果如下:

  1. <Person>
  2. <Name>yitian</Name>
  3. <NickName>易天</NickName>
  4. <Age>25</Age>
  5. <IdentityCode><![CDATA[10000]]></IdentityCode>
  6. <Birthday>1994/01/01</Birthday>
  7. </Person>
  8. Person(name=yitian, nickname=易天, age=25, identityCode=10000, birthday=1994-01-01)

如果取消那两行注释,那么运行结果如下。可以看到Jackson XML注解对生成的XML的控制效果。

  1. <Person birthday="1994/01/01">
  2. <Name>yitian</Name>易天
  3. <Age>25</Age>
  4. <IdentityCode><![CDATA[10000]]></IdentityCode>
  5. </Person>
  6. Person(name=yitian, nickname=null, age=25, identityCode=10000, birthday=1994-01-01)

7. Spring Boot集成

7.1 自动配置

Spring Boot对Jackson的支持非常完善,只要我们引入相应类库,Spring Boot就可以自动配置开箱即用的Bean。Spring自动配置的ObjectMapper(或者XmlMapper)做了默认的配置,基本上可以适应大部分情况。

  • 禁用了MapperFeature.DEFAULT_VIEW_INCLUSION,仅对@JsonView修饰的字段进行序列化
  • 禁用了DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,未知属性不报异常r
  • 禁用了SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,日期输出成format格式而不是时间戳

如果需要修改自动配置的ObjectMapper属性也非常简单,直接修改application.yml即可

  1. spring:
  2. jackson:
  3. #日期格式化
  4. date-format: yyyy-MM-dd HH:mm:ss
  5. serialization:
  6. #格式化输出
  7. indent_output: true
  8. #忽略无法转换的对象
  9. fail_on_empty_beans: false
  10. #设置空如何序列化
  11. defaultPropertyInclusion: NON_EMPTY
  12. deserialization:
  13. #允许对象忽略json中不存在的属性
  14. fail_on_unknown_properties: false
  15. parser:
  16. #允许出现特殊字符和转义符
  17. allow_unquoted_control_chars: true
  18. #允许出现单引号
  19. allow_single_quotes: true

由于Spring会同时配置相应的HttpMessageConverters,所以我们其实要做的很简单,用Jackson注解标注好要映射的Java类,然后直接让控制器返回对象即可!下面是一个Java类。然后是控制器代码。在整个过程中我们只需要引入Jackson类库,然后编写业务代码就好了。关于如何配置Jackson类库,我们完全不需要管,这就是Spring Boot的方便之处。

  1. @JsonRootName("person")
  2. public class Person {
  3. @JsonProperty
  4. private String name;
  5. @JsonProperty
  6. private int id;
  7. @JsonFormat(pattern = "yyyy-MM-DD")
  8. private LocalDate birthday;
  9. public Person(String name, int id, LocalDate birthday) {
  10. this.name = name;
  11. this.id = id;
  12. this.birthday = birthday;
  13. }
  14. }

7.2 手动配置

Spring Boot自动配置非常方便,但不是万能的。在必要的时候,我们需要手动配置Bean来替代自动配置的Bean。

  1. @Configuration
  2. public class JacksonConfig {
  3. @Bean
  4. @Primary
  5. @Qualifier("xml")
  6. public XmlMapper xmlMapper(Jackson2ObjectMapperBuilder builder) {
  7. XmlMapper mapper = builder.createXmlMapper(true)
  8. .build();
  9. mapper.enable(SerializationFeature.INDENT_OUTPUT);
  10. mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
  11. return mapper;
  12. }
  13. @Bean
  14. @Qualifier("json")
  15. public ObjectMapper jsonMapper(Jackson2ObjectMapperBuilder builder) {
  16. ObjectMapper mapper = builder.createXmlMapper(false)
  17. .build();
  18. mapper.enable(SerializationFeature.INDENT_OUTPUT);
  19. mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
  20. return mapper;
  21. }
  22. }

然后在需要的地方进行依赖注入。需要注意为了区分ObjectMapper和XmlMapper,需要使用@Qualifier注解进行标记。

  1. @Controller
  2. public class MainController {
  3. @Autowired @Qualifier("json")
  4. private ObjectMapper jsonMapper;
  5. @Autowired @Qualifier("xml")
  6. private XmlMapper xmlMapper;
  7. private Person person = new Person("yitian", 10000, LocalDate.of(1994, 1, 1));
  8. @RequestMapping(value = "/json", produces = "application/json")
  9. @ResponseBody
  10. public Person json() {
  11. return person;
  12. }
  13. }

参考

IBM Developer:Jackson 框架的高阶应用
https://www.ibm.com/developerworks/cn/java/jackson-advanced-application/index.html