在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 };
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提供注解来自定义编排和反编排的处理过程。
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<String,Object> userData = mapper.readValue(new File("user.json"), Map.class);
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)。