原來 Java 項目中用的 JSON 組件庫主要是 Gson 和 json-lib,Gson 算是很錯的庫,json-lib 略顯寒磣。好啦,最近 Play 2.x 中棄用了 Gson 而採納了 Jackson,所以現在就來打探一下 Jackson,踩個點吧。
Jackson 號稱非常高的性能,聽說比另兩位兄弟 Gson 和 json-lib 高出一大截,我沒有親測,可是有心人做了,看這個鏈接 兩款JSON類庫Jackson與JSON-lib的性能對比(新增第三款測試) 中的數據。2010 年 8 月份的測試結果,不知現在隨着版本的變更是否仍然保持着這種懸殊。
通常我會在把文章開頭塞豐滿,做足前戲,並不是因爲在天涯混習慣了的緣故,況且我在天涯總是訥於言的; 在這裏,自己的地盤自己作主,不會有要求碼足多少字才能發表的自虐性需求,僅僅是讓本文在主頁上顯示時的的概要不空洞而已,可以簡單粗暴的稱之爲廢話。
了了,先了解 Jacson 最貼近實際應用場景的應用,即 Jackson 怎麼把一個 Java 生成對應的 JSON 字符串,看看前面的文字有這麼多了,直接上一段代碼吧,而後再慢慢假設與分解:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | package cc.unmi.testjackson; import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility; import org.codehaus.jackson.annotate.JsonMethod; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializationConfig; /** * @author Unmi */ public class TestClient { /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(JsonMethod.FIELD, Visibility.ANY); mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true ); Person person = new Person( "Unmi" , new Address( "JiangXi" )); String json = mapper.writeValueAsString(person); System.out.println(json); } } class Person { private String name; private Address address; public Person(String name, Address address){ this .name = name; this .address = address; } } class Address { private String province; public Address(String province){ this .province = province; } public String getProvince(){ return this .province; } } |
要使用 Jackson,自然要引入相應的 jar 包,用 Maven 管理依賴的在 pom.xml 中加上:
1 2 3 4 5 | < dependency > < groupId >org.codehaus.jackson</ groupId > < artifactId >jackson-mapper-asl</ artifactId > < version >1.9.11</ version > </ dependency > |
這樣在項目中就會有了 jackson-mapper-asl-1.9.11.jar 和 jackson-core-asl-1.9.11.jar 這兩個 jar 包,其實當前版本是 2.1.0,只是 Maven 中央庫中還沒有。
上面代碼結果是可預測了,就是:
1 2 3 4 5 6 | { "name" : "Unmi" , "address" : { "province" : "JiangXi" } } |
Jackson 提供了 ObjectMapper 和 JsonGenerator 兩個 API 讓你去玩轉 JSON,其實內部實現肯定匯合在一起了,別讓兩個選擇就使你不會選擇了。
如果把第一段代碼中的第 20 行拿掉,運行會拋出異常:
Exception in thread “main” org.codehaus.jackson.map.JsonMappingException: No serializer found for class cc.unmi.testjackson.Person and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )
at org.codehaus.jackson.map.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:52)
at org.codehaus.jackson.map.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:25)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:610)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:256)
at org.codehaus.jackson.map.ObjectMapper._configAndWriteValue(ObjectMapper.java:2575)
at org.codehaus.jackson.map.ObjectMapper.writeValueAsString(ObjectMapper.java:2097)
原因是 Jackson 默認情況下不知道怎麼去序列化,解決辦法是:
1. 用上面的第 20 行代碼,setVisibility,這個屬性也可以通過 SerializationConfig 來設置
2. 或是給你要轉換 Json 的類加上註解 @JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.ANY)3. 也可以給字段或 getter 上加上 @JsonProperty 註解,還能用它指定序列化時的屬性名
第 21 行是輔助設置,對格式的控制等,比要不要輸出 null 值的,數字日期怎麼顯示,多瞧瞧那些 Feature:
org.codehaus.jackson.map.SerializationConfig.Feature.*
org.codehaus.jackson.JsonGenerator.Feature.*
org.codehaus.jackson.JsonParser.Feature.*
Jackson 的定製性還是很強的。再來想像這麼一個需求,想輸出的 JSON 屬性也可定製,比如不想 {“name”: “Unmi”},想要屬性的第一個字母是大寫的,{“Name”: “Unmi”}。沒問題,用 ObjectMapper 的
setPropertyNamingStrategy(PropertyNamingStrategy s) 方法
定義自己的 PropertyNamingStrategry, 可以繼承自 PropertyNamingStrategy 或 PropertyNamingStrategyBase,顯然繼承自 PropertyNamingStrategyBase 來簡單,因爲它幫你適配了,只需覆寫它的一個方法,如:
01 02 03 04 05 06 07 08 09 10 11 | import org.codehaus.jackson.map.PropertyNamingStrategy.PropertyNamingStrategyBase; public class CapitalizedPropertyNamingStrategy extends PropertyNamingStrategyBase { @Override public String translate(String propertyName) { String name = propertyName.replaceAll( "^\\w" , propertyName.toUpperCase().substring( 0 , 1 )); return name; } } |
然後對 mapper 調用
1 | mapper.setPropertyNamingStrategy( new CapitalizedPropertyNamingStrategy()); |
再次運行代碼,結果就變成了:
1 2 3 4 5 6 | { "Name" : "Unmi" , "Address" : { "Province" : "JiangXi" } } |
還有什麼需求,混跡代碼中,最不能缺的就是想像力了。
小結一下:
1. ObjectMapper 可用來序列化和反序列化對象,而 JsonParser 是用來反序列化的,JsonGenerator 是序列化的。若想讓代碼職責分明,就用 JsonParser 或 JsonGenerator 代替 ObjectMapper 來用
2. ObjectMapper 和 JsonGenerator 有各種各樣的 writeXxx() 方法,可以讓結果輸出到不同的目的地; 同樣 ObjectMapper 和 JsonParser 也有各色 readXxx() 方法,方便你從不同的源獲得數據
3. 多看看 Jackson 爲我們提供了註釋 Annotation,留意上面那幾個 Feature 中有些什麼枚舉值,這裏面也是我們的擴展點。