原来 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 中有些什么枚举值,这里面也是我们的扩展点。
参考: 1. JacksonInFiveMinutes