1. 程式人生 > >5分鐘學會Jackson

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 內容作為離散事件。

  • 樹模型 :提供一個 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 種方法的用法如下:

  • 流 API: 效能最佳的方式 (最低開銷、 速度最快的讀/寫; 其它二者基於它實現)。

  • 資料繫結 :使用最方便的方式。

  • 樹模型: 最靈活的方式。

鑑於這些特性,讓我們考慮以相反的順序,以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 像一個的顯式結構:

   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 的結構構建(或從 JSON繫結及修改),也可以如前法實現:
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)。