Jackson

670 阅读5分钟

Jackson

Jackson主要用于JSON和JavaBean之间的转换。

Jackson有以下特点:依赖少,简单易用,解析大Json速度快、内存占用比较低,拥有灵活的API,方便扩展与定制。

Jackson类库GitHub地址:github.com/FasterXML/j…

导入依赖

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.module</groupId>
  <artifactId>jackson-module-parameter-names</artifactId>
</dependency>

Jackson核心类ObjectMapper

jackson提供了三种JSON的处理方式,分别是:数据绑定,JSON树模型,流式API.其中前两项功能都是基于OjbectMapper 来实现的,而流式API功能则需要其更底层的JsonGenerator和JsonParse来实现

通常情况下我们使用ObjectMapper类

  • 从字符串、流或文件中解析JSON,并创建表示已解析的JSON的Java对象(反序列化)
  • 将Java对象构建成JSON字符串(序列化)
  • 将JSON 解析为自定义类的对象,也可以解析JSON树模型的对象

Jackson将JSON转换为JavaBean属性时,默认是通过Json字段的名称与Java对象中的get和set方法进行匹配进行绑定

Jackson取getter和setter方法名中去除get和set部分,并将首字母小写。与JSON中属性进行匹配

  • public修饰的属性可以进行序列化和反序列化
  • 属性私有,可根据提供的getter/setter方法,该属性可序列化和反序列化
  • 属性只有public的setter方法,只能用于反序列化

JavaBean转为JSON字符串

 User user = new User(1);
 ObjectMapper mapper = new ObjectMapper();
 mapper.findAndRegisterModules();
 String value = mapper.writeValueAsString(user);
 System.out.println(value);     //{"id":1}

JSON字符串转为JavaBean

转为普通对象

 String json = "{"id":"zhuan2quan"}";
 ObjectMapper mapper = new ObjectMapper();
 mapper.findAndRegisterModules();
 User value = mapper.readValue(json,User.class);   //User(id=zhuan2quan)

JSON转为集合

 String json = "{"id":"zhuan2quan","name":"程序新视界","interest":["Java","Spring Boot","JVM"]}";
         ObjectMapper mapper = new ObjectMapper();
         try {
             // 对泛型的反序列化,使用TypeReference可以明确的指定反序列化的类型。
             Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
             });
             System.out.println(map);
         } catch (JsonProcessingException e) {
             log.error("解析异常", e);
         }

JSON树模型

如果Json字符串比较大,则可使用Json数模型来灵活的获取所需的字段内容,在Jackson中提供了get、path、has等方法来获取或判断

 // 构建JSON树
     ObjectMapper mapper = new ObjectMapper();
     ObjectNode root = mapper.createObjectNode();
     root.put("id", "zhuan2quan");
     root.put("name", "程序新视界");
     ArrayNode interest = root.putArray("interest");
     interest.add("Java");
     interest.add("Spring Boot");
     interest.add("JVM");
     // JSON树转JSON字符串
     String json = null;
     try {
         json = mapper.writeValueAsString(root);
     } catch (JsonProcessingException e) {
 }
 System.out.println(json);
 //{"id":"zhuan2quan","name":"程序新视界","interest":["Java","Spring Boot","JVM"]}
 /**
      * 解析JSON字符串为JSON树模型
      */
 String json = "{"id":"zhuan2quan","name":"程序新视界","interest":["Java","Spring Boot","JVM"]}";
         ObjectMapper mapper = new ObjectMapper();
         try {
             // 将JSON字符串转为JSON树
             JsonNode jsonNode = mapper.readTree(json);
             String name = jsonNode.path("name").asText();
             System.out.println(name);
 ​
             JsonNode interestNode = jsonNode.get("interest");
             if (interestNode.isArray()){
                 for (JsonNode node : interestNode){
                     System.out.println(node.asText());
                 }
             }
         } catch (JsonProcessingException e) {
             log.error("Json Node转换异常", e);
         }
     }

注解的使用

上面通过统一配置可对全局格式的序列化和反序列化进行配置,但某些个别的场景下,需要针对具体的字段进行配置,这就需要用注解。比如当Json字符串中的字段与Java对象中的属性不一致时,就需要通过注解来建立它们直接的关系。

@JsonProperty,作用JavaBean字段上,指定一个字段用于JSON映射,默认情况下映射的JSON字段与注解的字段名称相同。可通过value属性指定映射的JSON的字段名称。

@JsonIgnore可用于字段、getter/setter、构造函数参数上,指定字段不参与序列化和反序列化。

@JsonIgnoreProperties作用于类上,序列化时@JsonIgnoreProperties({"prop1", "prop2"})会忽略pro1和pro2两个属性。反序列化时@JsonIgnoreProperties(ignoreUnknown=true)会忽略类中不存在的字段。

@JsonFormat作用于字段上,通常用来进行格式化操作。

 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
 private Date date;

如果JavaBean中的时间字段使用的是JDK8新增的时间日期(LocalDate/LocalTime/LocalDateTime)类型的话,需要添加jackson-datatype-jsr310依赖。在讲依赖部分时,SpringBoot默认引入的依赖中就有这个。

当然,还有一些其他的注解,比如@JsonPropertyOrder、@JsonRootName、@JsonAnySetter、@JsonAnyGetter、@JsonNaming等,当使用时参考对应的文档和示例看一下就可以,这里就不再一一列出了。

自定义解析器

如果上面的注解和统一配置还无法满足需求,可自定义解析器,示例如下:

 public class MyFastjsonDeserialize extends JsonDeserializer<Point> {
 ​
     @Override
     public Point deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
         JsonNode node = jsonParser.getCodec().readTree(jsonParser);
         Iterator<JsonNode> iterator = node.get("coordinates").elements();
         List<Double> list = new ArrayList<>();
         while (iterator.hasNext()) {
             list.add(iterator.next().asDouble());
         }
         return new Point(list.get(0), list.get(1));
     }
 }

定义完成之后,注册到Mapper中:

 ObjectMapper objectMapper = new ObjectMapper();
 SimpleModule module = new SimpleModule();
 module.addDeserializer(Point.class, new MyFastjsonDeserialize());
 objectMapper.registerModule(module);

Jackson处理XML

Jackson也可以通过jackson-dataformat-xml包提供了处理XML的功能。在处理XML时建议使用woodstox-core包,它是一个XML的实现,比JDK自带XML实现更加高效,也更加安全。

 <dependency>
     <groupId>com.fasterxml.jackson.dataformat</groupId>
     <artifactId>jackson-dataformat-xml</artifactId>
 </dependency>

如果使用Java 9及以上版本,可能会出现java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException异常,这是因为Java 9实现了JDK的模块化,将原本和JDK打包在一起的JAXB实现分隔出来。所以需要手动添加JAXB的实现。

 <dependency>
     <groupId>javax.xml.bind</groupId>
     <artifactId>jaxb-api</artifactId>
 </dependency>

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

 @Test
 public void testXml(){
     WeChat weChat = new WeChat();
     weChat.setId("zhuan2quan");
     weChat.setName("程序新视界");
     weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
 ​
     XmlMapper xmlMapper = new XmlMapper();
     try {
         String xml = xmlMapper.writeValueAsString(weChat);
         System.out.println(xml);
     } catch (JsonProcessingException e) {
         e.printStackTrace();
     }
 }

执行之后,输出结果:

 <WeChat>
     <id>zhuan2quan</id>
     <name>程序新视界</name>
     <interest>
         <interest>Java</interest>
         <interest>Spring Boot</interest>
         <interest>JVM</interest>
     </interest>
 </WeChat>

Spring Boot中的集成

在最开始的时候,我们已经看到Spring Boot默认引入了Jackson的依赖,而且也用我们做什么额外的操作,其实已经在使用Jackson进行Json格式的数据与MVC中参数进行绑定操作了。

如果Spring Boot默认的配置并不适合项目需求,也可以通过内置的配置进行配置,以application.yml配置为例,可通过指定以下属性进行相应选项的配置:

 #指定日期格式,比如yyyy-MM-dd HH:mm:ss,或者具体的格式化类的全限定名
 spring.jackson.date-format 
 #是否开启Jackson的反序列化
 spring.jackson.deserialization
 #是否开启json的generators.
 spring.jackson.generator
 #指定Joda date/time的格式,比如yyyy-MM-ddHH:mm:ss). 如果没有配置的话,dateformat会作为backup
 spring.jackson.joda-date-time-format
 #指定json使用的Locale.
 spring.jackson.locale
 #是否开启Jackson通用的特性.
 spring.jackson.mapper
 #是否开启jackson的parser特性.
 spring.jackson.parser
 #指定PropertyNamingStrategy(CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES)或者指定PropertyNamingStrategy子类的全限定类名.
 spring.jackson.property-naming-strategy
 #是否开启jackson的序列化.
 spring.jackson.serialization
 #指定序列化时属性的inclusion方式,具体查看JsonInclude.Include枚举.
 spring.jackson.serialization-inclusion
 #指定日期格式化时区,比如America/Los_Angeles或者GMT+10.
 spring.jackson.time-zone

Spring Boot自动配置非常方便,但某些时候需要我们手动配置Bean来替代自动配置的Bean。可通过如下形式进行配置:

 @Configuration
 public class JacksonConfig {
     @Bean
     @Qualifier("json")
     public ObjectMapper jsonMapper(Jackson2ObjectMapperBuilder builder) {
         ObjectMapper mapper = builder.createXmlMapper(false)
                 .build();
         mapper.enable(SerializationFeature.INDENT_OUTPUT);
         mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
         return mapper;
     }
 }

配置完成,可在使用的地方直接注入即可:

 @Resource
 private ObjectMapper jsonMapper;

对于上面的注入,可能会有朋友问了,是否有线程安全的问题?这个不用担心ObjectMapper是线程安全的。

\