5分钟学会Jackson

Java JSON(二)5分钟学会Jackson



在Java平台(StAX, JAXB等)XML处理质量和多样化的激励下,Jackson为多功能的Java JSON处理包其目标为集快捷、正确、轻量和符合人体工程学与一体。

本文将给出Jackson的功能概览。

JSON的三种处理方式   
Jackson提供了三种可选的JSON处理方法(一种方式及其两个变型):

  • 流式 API : (也称为"增量分析/生成") 读取和写入 JSON 内容作为离散事件。

    • org.codehaus.jackson. JsonParser 读, org.codehaus.jackson. JsonGenerator 写。

    • 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 种方法的用法如下:

  • 流 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 }; 

4 public static class Name { 
5 private String _first, _last; 

7 public String getFirst() { return _first; } 
8 public String getLast() { return _last; } 

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)。

你可能感兴趣的:(java设计模式,开发者)