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 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索