5分鐘學會Jackson
轉自:http://www.blogjava.net/wangxinsh55/archive/2012/09/06/387180.html
在Java平臺(StAX, JAXB等)XML處理質量和多樣化的激勵下,Jackson為多功能的Java JSON處理包其目標為集快捷、正確、輕量和符合人體工程學與一體。
本文將給出Jackson的功能概覽。
JSON的三種處理方式
Jackson提供了三種可選的JSON處理方法(一種方式及其兩個變型):
-
流式 API:(也稱為"增量分析/生成") 讀取和寫入 JSON 內容作為離散事件。
-
org.codehaus.jackson.JsonParser 讀, org.codehaus.jackson.
-
StAX API 的激勵。
-
-
樹模型 :提供一個 JSON 文件可變記憶體樹的表示形式。
-
org.codehaus.jackson.map.ObjectMapper 生成樹 ;樹組成 JsonNode 節點集。
- 樹模型類似於 XML DOM。
-
-
資料繫結: JSON和POJO相互轉換,基於屬性訪問器規約或註解。
-
有 兩種變體: 簡單 和 完整 的資料繫結:
-
簡單資料繫結: 是指從Java Map、List、String、Numbers、Boolean和空值進行轉換
-
完整資料繫結 :是指從任何 Java bean 型別 (及上文所述的"簡單"型別) 進行轉換
-
org.codehaus.jackson.map.ObjectMapper 對兩個變種,進行編組(marshalling )處理 (寫入 JSON) 和反編組(unmarshalling ,讀 JSON)。
-
JAXB激勵下的基於註釋的 (程式碼優先)變種。
-
從使用的角度來看,總結這些3 種方法的用法如下:
鑑於這些特性,讓我們考慮以相反的順序,以Java開發人員最自然和方便的方法開始使用: 傑Jackson資料繫結 API。
Jackson的 org.codehaus.jackson.map.ObjectMapper "只是"將JSON 資料對映為POJO 物件 。例如,給定 JSON 資料:
{
"name" : { "first" : "Joe", "last" : "Sixpack" },
"gender" : "MALE",
"verified" : false,
"userImage" : "Rm9vYmFyIQ=="
}
用兩行程式碼把它變成一個使用者例項:
1 ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally
2 User user = mapper.readValue(new File("user.json"), User.class);
使用者類大致如下(源自另一部落格):
1 public class User {
2 public enum Gender { MALE, FEMALE };
3
4 public static class Name {
5 private String _first, _last;
6
7 public String getFirst() { return _first; }
8 public String getLast() { return _last; }
9
10 public void setFirst(String s) { _first = s; }
11 public void setLast(String s) { _last = s; }
12 }
13
14 private Gender _gender;
15 private Name _name;
16 private boolean _isVerified;
17 private byte[] _userImage;
18
19 public Name getName() { return _name; }
20 public boolean isVerified() { return _isVerified; }
21 public Gender getGender() { return _gender; }
22 public byte[] getUserImage() { return _userImage; }
23
24 public void setName(Name n) { _name = n; }
25 public void setVerified(boolean b) { _isVerified = b; }
26 public void setGender(Gender g) { _gender = g; }
27 public void setUserImage(byte[] b) { _userImage = b; }
28 }
編組為JSON同樣簡單:
mapper.writeValue(new File("user-modified.json"), user);
對於更復雜的資料繫結 (例如,反編排格式日期到 java.util.Date),Jackson提供註解來自定義編排和反編排的處理過程。
簡單的資料繫結示例 如果你沒有 (或不想建立)從 JSON到 Java 的相互轉化類,簡單資料繫結可能是更好的方法。它用相同方式實現完整的資料繫結,除非形式化繫結型別只指定為 Object.class (或 Map.class, List.class,即使需要更多的型別定義)。因此早期繫結JSON的使用者資料可能如此實現:Map<String,Object> userData = mapper.readValue(new File("user.json"), Map.class);
userData 像一個的顯式結構:
這顯然是雙向的: 如果利用諸如Map 的結構構建(或從 JSON繫結及修改),也可以如前法實現:1 Map<String,Object> userData = new HashMap<String,Object>(); 2 Map<String,String> nameStruct = new HashMap<String,String>(); 3 nameStruct.put("first", "Joe"); 4 nameStruct.put("last", "Sixpack"); 5 userData.put("name", nameStruct); 6 userData.put("gender", "MALE"); 7 userData.put("verified", Boolean.FALSE); 8 userData.put("userImage", "Rm9vYmFyIQ==");
Map<String,Object> userData = mapper.readValue(new File("user.json"), Map.class);將如何工作呢? 只定義了Map.class,未定義一般的Key/valie型別,但ObjectMapper 卻知曉與Map(及List、陣列、wrapper型別 )之間如何相互轉換,僅是如此即可。如果它可以正確地對映到您所提供的型別,它將被對映。 Jackson將使用簡單資料繫結的具體Java 型別包括:
JSON Type | Java Type |
object | LinkedHashMap<String,Object> |
array | ArrayList<Object> |
string | String |
number(no fraction) | Integer, Long or BigInteger (smallest applicable) |
number (fraction) | BigDecimal |
true|false | boolean |
null | null |
泛型的資料繫結
除繫結到POJO和簡單型別外,還有一個額外的變型:繫結到泛型(型別)容器。此時,由於所謂的型別擦除(Java採用向後相容的方式實現泛型),需要進行特殊處理,以防止使用類似 Collection<String>.class(不被編譯)。
所以,熱想繫結資料島Map<String,User>,方式如下:
Map<String,User> result = mapper.readValue(src, new TypeReference<Map<String,User>>() { });
其中TypeReference只需傳入泛型型別即可(此時需要匿名內部類):重要部分為<Map<String,User>>,定義要繫結的資料型別。
若不如此(僅定義Map.class),其呼叫等價於繫結到 Map<?,?>(亦即 “untyped” Map),如前所述。
更新:1.3版的Jackson允許利用TypeFactory實現構造型別。
樹模式示例
另一種從JSON獲取物件方式是構造“樹”,類似於XML的DOM樹。Jackson構造樹的方法利用JsonNode基類,其中包含公開的通常所需的讀取訪問方法,實際所用的節點型別為其子類;但子型別僅在修改樹時需要。
JSON樹可通過流式API或ObjectMapper方式讀、寫。
利用 ObjectMapper,方法如下:
1 ObjectMapper m = new ObjectMapper();
2 // can either use mapper.readTree(JsonParser), or bind to JsonNode
3 JsonNode rootNode = m.readValue(new File("user.json"), JsonNode.class);
4 // ensure that "last name" isn't "Xmler"; if is, change to "Jsoner"
5 JsonNode nameNode = rootNode.path("name");
6 String lastName = nameNode.path("last").getTextValue().
7 if ("xmler".equalsIgnoreCase(lastName)) {
8 ((ObjectNode)nameNode).put("last", "Jsoner");
9 }
10 // and write it out:
11 m.writeValue(new File("user-modified.json"), rootNode);
或你想馬上構造一棵樹,方法如下:
1 TreeMapper treeMapper = new TreeMapper();
2 ObjectNode userOb = treeMapper.objectNode();
3 Object nameOb = userRoot.putObject("name");
4 nameOb.put("first", "Joe");
5 nameOb.put("last", "Sixpack");
6 userOb.put("gender", User.Gender.MALE.toString());
7 userOb.put("verified", false);
8 byte[] imageData = getImageData(); // or wherever it comes from
9 userOb.put("userImage", imageData);
(注意: Jackson 1.2可直接使用ObjectMapper:通過ObjectMapper.createObjectNode()建立userOb -- 上例工作於Jackson 1.0 和 1.1)。
流式 API 示例
最後,還有第三種方式: 渦輪增壓、 高效能的方法稱為流 API (或增量模式,因為內容是增量讀取和寫入的)。
只是為了好玩,讓我們實現使用"原生"Stream API 的寫入功能 (相當於前面示例): WriteJSON.java
1 JsonFactory f = new JsonFactory();
2 JsonGenerator g = f.createJsonGenerator(new File("user.json"));
3
4 g.writeStartObject();
5 g.writeObjectFieldStart("name");
6 g.writeStringField("first", "Joe");
7 g.writeStringField("last", "Sixpack");
8 g.writeEndObject(); // for field 'name'
9 g.writeStringField("gender", Gender.MALE);
10 g.writeBooleanField("verified", false);
11 g.writeFieldName("userImage"); // no 'writeBinaryField' (yet?)
12 byte[] binaryData = ...;
13 g.writeBinary(binaryData);
14 g.writeEndObject();
15 g.close(); // 重要:強制寫入輸出,並關閉輸出流!
非常不錯 (尤其是相對寫入所需的工作量,亦即等效的 XML 內容),但肯定比基本物件對映更辛苦。
另一方面,必須完全控制每一個細節。開銷很小: 這仍然快於使用 ObjectMapper;並非快很多 ,但還是要快些(一般快或許 20-30%)。也許最重要的是,以流方式輸出: 除一些緩衝外,所有內容都將馬上輸出。這意味著該方式記憶體使用量也是最小的。
然後如何解析呢?程式碼可能看起來類似:
1 JsonFactory f = new JsonFactory();
2 JsonParser jp = f.createJsonParser(new File("user.json"));
3 User user = new User();
4 jp.nextToken(); // will return JsonToken.START_OBJECT (verify?)
5 while (jp.nextToken() != JsonToken.END_OBJECT) {
6 String fieldname = jp.getCurrentName();
7 jp.nextToken(); // move to value, or START_OBJECT/START_ARRAY
8 if ("name".equals(fieldname)) { // contains an object
9 Name name = new Name();
10 while (jp.nextToken() != JsonToken.END_OBJECT) {
11 String namefield = jp.getCurrentName();
12 jp.nextToken(); // move to value
13 if ("first".equals(namefield)) {
14 name.setFirst(jp.getText());
15 } else if ("last".equals(namefield)) {
16 name.setLast(jp.getText());
17 } else {
18 throw new IllegalStateException("Unrecognized field '"+fieldname+"'!");
19 }
20 }
21 user.setName(name);
22 } else if ("gender".equals(fieldname)) {
23 user.setGender(Gender.valueOf(jp.getText()));
24 } else if ("verified".equals(fieldname)) {
25 user.setVerified(jp.getCurrentToken() == JsonToken.VALUE_TRUE);
26 } else if ("userImage".equals(fieldname)) {
27 user.setUserImage(jp.getBinaryValue());
28 } else {
29 throw new IllegalStateException("Unrecognized field '"+fieldname+"'!");
30 }
31 }
32 jp.close(); // ensure resources get cleaned up timely and properly
這是不是您將更多使用的資料繫結方法。
最後提醒的一個竅門: 可可能通過JsonParser 和 JsonGeneratorit 直接實現資料繫結和樹模式。請參閱如下方法:
- JsonParser.readValueAs()
- JsonParser.readValueAsTree()
- JsonGenerator.writeObject()
- JsonGenerator.writeTree()
將實現你期望的結果。
切記,確保所用的 org.codehaus.jackson.map.MappingJsonFactory是"適用資料繫結“的解析器和生成器例項(而非基本的org.codehaus.jackson.JsonFactory)。