Jackson学习笔记

老版本的Jackson使用的包名为org.codehaus.jackson,而新版本使用的是com.fasterxml.jacksonjavascript

Jackson主要包含了3个模块:css

  • jackson-core
  • jackson-annotations
  • jackson-databind
    其中,jackson-annotations依赖于jackson-core,jackson-databind又依赖于jackson-annotations。

Jackson有三种方式处理Json:java

  1. 使用底层的基于Stream的方式对Json的每个小的组成部分进行控制
  2. 使用Tree Model,经过JsonNode处理单个Json节点
  3. 使用databind模块,直接对Java对象进行序列化和反序列化

一般来讲,咱们在平常开发中使用的是第3种方式,有时为了简便也会使用第2种方式,好比你要从一个很大的Json对象中只读取那么一两个字段的时候,采用databind方式显得有些重,JsonNode反而更简单。shell

使用ObjectMapper

本文的例子以jackson-databind-2.9.4为例。
建立Person类以下:数据库

public class Person { private String name; private String address; private String mobile; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } } 

序列化一个Person对象:json

public static void main(String[] args) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); Person person = new Person(); person.setName("davenkin"); person.setAddress(""); System.out.println(objectMapper.writeValueAsString(person)); } 

返回结果:bash

{"name":"davenkin","address":"","mobile":null} 

在默认状况下,ObjectMapper在序列化时,将全部的字段一一序列化,不管这些字段是否有值,或者为null。app

另外,序列化依赖于getter方法,若是某个字段没有getter方法,那么该字段是不会被序列化的。因而可知,在序列化时,OjbectMapper是经过反射机制找到了对应的getter,而后将getter方法对应的字段序列化到Json中。请注意,此时ObjectMapper并不真正地检查getter对应的属性是否存在于Person对象上,而是经过getter的命名规约进行调用,好比对于getAbc()方法:函数

public String getAbc(){ return "this is abc"; } 

即使Person上没有abc属性,abc也会被序列化:ui

{"name":"davenkin","address":"","mobile":null,"abc":"this is abc"} 

反序列化一个Json字符串,其中少了一个mobile字段:

public static void main(String[] args) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); Person person = objectMapper.readValue("{\"name\":\"davenkin\",\"address\":\"\"}", Person.class); System.out.println("name: " + person.getName()); System.out.println("address: " + person.getAddress()); System.out.println("mobile: " + person.getMobile()); } 

输出:

name: davenkin
address: 
mobile: null 

能够看出,少了mobile字段,程序依然正常工做,只是mobile的值为null。

另外,若是咱们向Json中增长一个Person中没有的字段:

public static void main(String[] args) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); Person person = objectMapper.readValue("{\"name\":\"davenkin\",\"address\":\"\",\"mobile\":null,\"extra\":\"extra-value\"}", Person.class); System.out.println("name: " + person.getName()); System.out.println("address: " + person.getAddress()); System.out.println("mobile: " + person.getMobile()); } 

此时运行程序将报错:

Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "extra" (class com.shell.b2b.factory.model.Person), not marked as ignorable (3 known properties: "mobile", "name", "address"]) 

表示在Person对象上找不到对应的extra属性,可是若是咱们在Person上增长一个空的setter:

public void setExtra(String extra) { } 

那么此时运行成功,因而可知OjbectMapper是经过反射的机制,经过调用Json中字段所对应的setter方法进行反序列化的。而且此时,依赖于Person上有默认构造函数。

综上,在默认状况下(即不对ObjectMapper作任何额外配置,也不对Java对象加任何Annotation),ObjectMapper依赖于Java对象的默认的无参构造函数进行反序列化,而且严格地经过getter和setter的命名规约进行序列化和反序列化。

去除getter和setter

纯粹地为了技术方面的缘由而添加getter和setter是很差的,能够经过如下方式去除掉对getter和setter的依赖:

objectMapper.setVisibility(ALL, NONE) .setVisibility(FIELD, ANY); 

ObjectMapper将经过反射机制直接操做Java对象上的字段。

此时建立Person以下:

public class Person { private String name; private String address; private String mobile; public Person(String name, String address, String mobile) { this.name = name; this.address = address; this.mobile = mobile; } } 

序列化Person:

public static void main(String[] args) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(ALL, NONE) .setVisibility(FIELD, ANY); Person person = new Person("name", "address", "mobile"); System.out.println(objectMapper.writeValueAsString(person)); } 

然而,此时反序列化的时候报错:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.shell.b2b.factory.model.Person` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) 

这是由于ObjectMapper在为字段设值以前,没法初始化Person对象,此时有两种解决方式:

  1. 为Person增长默认构造函数:
private Person() { } 

请注意,此时请将该构造函数设置为private的,由于咱们不想由于纯技术缘由而向外暴露一个会将Person类置于非法状态的构造函数(一个没有名字的Person还有什么用?)。

  1. 在已有构造函数上加上@JsonCreator注解,一般与@JsonProperty一块儿使用:
@JsonCreator public Person(@JsonProperty("name") String name, @JsonProperty("address") String address, @JsonProperty("mobile") String mobile) { this.name = name; this.address = address; this.mobile = mobile; } 

忽略字段

@JsonIgnore用于字段上,表示该字段在序列化和反序列化的时候都将被忽略。

@JsonIgnoreProperties主要用于类上:

@JsonIgnoreProperties(value = {"mobile","name"},ignoreUnknown = true) 

表示对于mobile和name字段,反序列化和序列化均忽略,而对于Json中存在的未知字段,在反序列化时忽略,ignoreUnknown不对序列化起效。

序列化时排除null或者空字符串

@JsonInclude(JsonInclude.Include.NON_NULL) public class Person { 

表示在序列化Person时,将值为null的字段排除掉。

@JsonInclude(JsonInclude.Include.NON_EMPTY) public class Person { 

表示在序列化Person时,将值为null的字段或者空的字符串字段排除掉。

用某个方法的返回值序列化整个对象

有时咱们并不想讲对象的全部字段都序列化,而是但愿用一个方法的返回值来序列化,好比toString()方法,此时能够用@JsonValue

@JsonValue public String toString(){ return "someValue"; } 

此时整个对象在序列化后的值将变为“someValue”,须要注意的是,这种方式下,在反序列化时也须要有响应的机制。

Java 8 Data/Time API支持

Java 8中引入了全新的时间处理API,好比在多数状况下咱们将使用Instant来表示某个事件发生的时间,Instant取代了以前的Timestamp。

可是,若是咱们直接使用ObjectMapper来序列化Instant:

public static void main(String[] args) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); System.out.println(objectMapper.writeValueAsString(Instant.now())); } 

将获得:

{"epochSecond":1520621382,"nano":959000000} 

一个Instant须要两个字段来表示,一样的状况也出如今LocalDateTime上,即一个时间须要多个字段表示。

为了克服这种状况,能够引入Jackson的DataType模块:

compile('com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4') 

而后配置ObjectMapper:

objectMapper.findAndRegisterModules(); 

此时,Instant将会被序列化为:

1520621578.637000000 

若是你以为这个数字不表意,那么能够:

objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 

此时,Instant被序列化为:

"2018-03-09T18:53:48.214Z" 

但这是一个UTC时区的时间,所以还不如直接使用数字呢。若是实在不喜欢数字,那么能够设置ObjectMapper的时区:

objectMapper.setTimeZone(getTimeZone(of("Asia/Shanghai"))); 

奇怪的是,这里必须显式地在ObjectMapper上设置,修改系统的默认时区对此时不生效的:

TimeZone.setDefault(getTimeZone(of("Asia/Shanghai"))); 

使用Jackson的推荐配置

  1. 对于全部的使用ObjectMapper的地方,推荐采用直接处理字段的方式,即:
objectMapper.setVisibility(ALL, NONE) .setVisibility(FIELD, ANY); 

另外,推荐加入Datatype模块。

在任什么时候候不要使用@JsonInclude(JsonInclude.Include.NON_EMPTY)@JsonInclude(JsonInclude.Include.NON_NULL)

  1. 对于接收客户端传来的请求(好比使用Spring MVC的Controller),使用@JsonCreator,并限制必须存在的字段为(require = true):
@JsonCreator public Person(@JsonProperty(value = "name", required = true) String name, @JsonProperty(value = "address", required = true) String address, @JsonProperty(value = "mobile", required = true) String mobile) { this.name = name; this.address = address; this.mobile = mobile; } 
  1. 若是要调用第三方的API,可使用经过@JsonIgnoreProperties(ignoreUnknown = true)忽略掉全部不须要的字段。

  2. 若是要将Java对象持久化为Json格式,即对于采用NoSQL的系统而言,数据格式(schema)都被隐藏在了代码模型(Java的类)中,此时须要注意如下几点:

  • 在序列化时,不要使用@JsonInclude(JsonInclude.Include.NON_NULL)或者`@JsonInclude(JsonInclude.Include.NON_EMPTY),由于这样所产生的Json不能完整地反应数据schema。也即,在任什么时候候,代码模型中的全部字段都必须序列化到数据库中,包含了null值,空字符串等。

  • 因为软件在开发过程当中常常会有数据库迁移,所以为了保证迁移以后的数据知足schema,咱们须要保证迁移以后的数据字段和代码模型中的字段是严格一一对应的,不能多,也不能少,所以建议使用私有(表示只为技术而存在)的@JsonCreator构造函数和@JsonProperty(value = "name", required = true)来反序列化对象。

做者:无知者云 连接:https://www.jianshu.com/p/4bd355715419 來源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。
相关文章
相关标签/搜索