Java JSON技术框架选型、测试及简单使用
(本文转载自’http://bbs.jee-soft.cn:8086/showtopic-155.aspx‘)
一、 JSONJSON英文全称为JavaScript Object Natation,采用key:value键值对的方式存贮数据,与xml格式相比,JSON是一种轻量级的数据交换格式;不要被Javascript这个单词迷惑,实际上JSON只是一种数据格式,与具体语言并无关系。JSON已被广泛应用于业界,比如目前NoSQL数据库存贮大都采用key:value存贮结构,以Mongo为例,其脚本语法甚至直接使用Javascript;在数据传输时,采用JSON格式也被广泛应用,大部分开放API都开放JSON模式的数据输出;在ajax请求数据时,json格式也被广泛推荐。json更多信息的可以查看json官方网站http://json.org。
二、 Java transient 关键字Json开源项目非常多,如org.json、 JSON-Lib、jsontool、Jackson、Gson、SimpleJSON等等,后来专门查看了几种json开源测试数据对比后,决定采用Jackson。展示两组测试数据。首先来看大侠wangym(原博客http://wangym.iteye.com/blog/738933)对Jackson、JSON-Lib、Gson的测试结果
JSON转Bean,5个线程并发,约200字节对象,1千万次转换:
Jackson | JSON-lib | Gson | |
吞吐量 | 64113.7 | 8067.4 | 13952.8 |
总耗时(秒) | 155 | 1238 | 700 |
Bean转JSON,5个线程并发,约200字节对象,1千万次转换:
Jackson | JSON-lib | Gson | |
吞吐量 | 54802 | 15093.2 | 17308.2 |
总耗时(秒) | 181 | 661 | 560 |
下面是参考Fastjson 的测试数据(http://www.iteye.com/topic/1113183)
性能对比
测试案例 | JSON-Lib | Simple-JSON | Fastjson | Jackson |
IntArray1000Decode | 3,626 | 1,431 | 563 | 596 |
StringArray1000Decode | 2,698 | 2,283 | 677 | 774 |
Map100StringDecode | 515 | 597 | 208 | 230 |
功能对比
特性 | JSON-Lib | Simple-JSON | Fastjson | Jackson |
序列化支持数组 | 不支持 | 不支持 | 支持 | 支持 |
序列化支持Enum | 不支持 | 不支持 | 支持 | 支持 |
支持JavaBean | 不直接支持 | 不直接支持 | 支持 | 支持 |
支持hibernate laze | 不直接支持 | 不直接支持 | 支持 | 支持 |
循环引用 | 支持 | 支持 | 支持 | 支持 |
测试总结:
最近在研究JSON,Java中有很多处理JSON的类库,lib-json、sf-json、fastjson还有Jackson Json。第一个就不说了,性能和功能都没有什么亮点。
sf-json最大的优点就是随机读取方便。代码很简单:
JSONObject json= JSONObject.fromObject(str);
然后读取字段内容:
json.getString或者getInt之类的。但是工作效率有待商榷,而且容易出错。
另外sf-json还有个优点就是自动使用unicode编码,当内容中出现中文或者符号的时候会自动将其转换为\uFFFF这样的unicode编码。这样即便是在web服务器端的response中没有设置编码,直接推送json也不会出现乱码问题。
fastjson,顾名思义就是快。网上已经有很多性能对比的数据了,我就不多说其性能了。
这里要说的就是它的功能性问题。可能是定位不一样,最初fastjson就是要快,因此在对象的序列化与反序列化上下了很大功夫。但是在功能上有所缺乏。
不知在哪个版本开始加上了key按字典排序的功能。但是貌似这个功能没有办法关闭。有些时候我是不希望字段顺序被打乱的,这个问题就无法解决。
我使用的fastjson版本为1.1.14。另外fastjson还有一些bug没有解决,而且是比较明显的bug。例如在@JsonField注解中format参数,这个是用来指定Date类型数据如何序列化的。如果你使用英文或符号,OK,没有问题(例如yyyy-MM-dd),但是格式中一旦出现中文就会出错(例如yyyy年MM月dd日)。而且经过实验,所有的注解都要放在属性的Getter(就是getXXX()方法)上,直接放在属性上是无法工作的。在eclipse中,一般我们都是直接写属性,属性写完后用自动生成的方式生成Getter和Setter方法。如果今后该类的属性发生变化了,个人更倾向于直接删除所有Getter和Setter,然后重新生成。那么假如把注解全放到Getter上面,我删的时候就要非常小心。
再有一个比较致命的就是文档。几乎找不到全面的文档来介绍或支持fastjson。整个项目都由一个名为“温少”的人来负责,存在很多不确定的因素。
经过个人的评估,我更倾向于使用Jackson Json。首先说文档,Jackson Json官方网站上对每一个版本都有详尽的文档(http://jackson.codehaus.org/)。另外Jackson Json的序列化与反序列化速度也并不见得有多慢。更重要的是它的注解支持要好于fastjson。就拿刚才说到的key按字典排序的功能吧,可以在实体类上直接加上@JsonPropertyOrder(alphabetic=false)注解就可以关闭排序功能。而对于其他功能的注解支持也很好。
例如Date的序列与反序列化注解支持
@JsonSerialize(using=DateSerializer.class)
@JsonDeserialize(using=DateDeserializer.class)
private Date birthday;
这样就能指定对birthday字段的序列化与反序列化方法。另外,这两个注解都直接放在了属性上,没有放在Getter上。
针对上面的两个注解,我的序列化器是这样写的
public class DateSerializer extends JsonSerializer<Date>
继承了JsonSerializer,泛型中指定了序列化类型为Date,然后重写如下方法
@Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException
方法中传进来的date就是将要被序列化的数据,接下来你可以任意展示该数据,在退出该方法之前使用gen.writeString(formattedDate);来完成序列化就可以了。
类似地,我的反序列化器是这样写的:
public class DateDeserializer extends JsonDeserializer<Date>
继承了JsonDeserializer,泛型中指定了反序列化类型为Date,然后重写如下方法
@Override
public Date deserialize(JsonParser parser, DeserializationContext context)throws IOException, JsonProcessingException {
这里面方法的返回值就是反序列化后的最终内容。方法内部你可以使用parser.getText()来获取到当前要处理的内容。你可以随便折腾里面的数据,只需要最后返回你想要的Date就可以了。
另外在制作基于Jackson Json的Service时想使用泛型的思想来写一个接口,最终目的就是希望方法能随着参数类型不同,返回值的类型也随之不同。以前很少写泛型的方法,这个问题虽然基础,但是难住了我,经过查看Jackson Json的源代码,我得到了启示,像下面这样写就OK了:
public <T> T strToObj(String jsonStr, Class<T> clazz)
这样写就可以了。假设我有一个Result类型的对象需要反序列化,当前已经有了一个json字符串jsonStr,那么我只需要指定第二个参数clazz就可以直接得到Result类型的对象了:
这样就不用在方法前加入(Result)类型强制转换了。
五、 Jackson 区别于竞争对手的7个杀手锏关于对象转json和json就不在这解介绍了,一下主要介绍常用的特殊功能,(具体你可以看一下封装的jacksons的类的方法 在下面打包)
1.使用Jackson时转换JSON时,日期格式设置:使用Jackson转化JSON对象的时候出现: 显示的时候,日期始终显示不正确,输出的日期是一串数字代表的时间戳,不符合要求,所以想到Jackson应当有方法设置输出的日期格式。后来一查果然有两种方式来实现
①普通的方式:
默认是转成timestamps形式的,通过下面方式可以取消timestamps。
objectMapper.configure(Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT,false);
这样将使时间生成使用所谓的使用 [ISO-8601 ]-compliant notation, 输出类似如下格式的时间: "1970-01-01T00:00:00.000+0000".
当然也可以自定义输出格式:
可以支持序列号(@JsonSerialize(using=CustomDateSerializer.class))
和反序列化(@JsonDeserialize(using=CustomDateDeserializer.class))
先定义自己需要的格式,例如:
然后在你的POJO上找到日期的属性上加上:
2.annotation(注解)、属性的过滤
① @JsonAutoDetect (class)这是作用于类的annotation,主要用于指明该类使用annotation,并且可以自动侦测getter,setter,构造方法,以便生成json对象
② @JsonIgnore (method/field):作用于方法或字段,用来表明,当生成json的时候忽略有该annotation的方法或字段
③ @JsonIgnoreProperties (value = { "hibernateLazyInitializer" , "password" }) ,主要用于过滤掉一些不需要的属性
参考(http://hi.baidu.com/suofang/blog/item/c4f3a351d83a9faf8d543069.html)
3.设置循环策略 :解决json最头疼的问题死循环能够干净地处理循环类型的某些子集,成为父/子链接。 这些都是互相紧密耦合的引用,其中两个对象以层次结构的方式交叉引用,如父/子树节点的关联,或更常见的,ORM采用的表间连接(join)的描述。
对引用问题(或更普遍的,循环引用),JSON没有处理它们的自然方法。,不像Java对象,没有标识信息可用。
常用的解决方法是只标记一个被忽略的引用(Jackson可通过使用@ JsonIgnore注解实现),但其缺点是反序列化时会丢失实际的耦合目标。
Jackson 有简单的基于注解的解决该问题的方案:两个引用需要一个注解(对“子”连接作@JsonManagedReference 注解,对“父”或“返回”连接作@JsonBackReference注解),并在此基础,Jackson知道要省略反向引用的序列化,但反序列化对象时要恢复它。此方式适用于典型的ORM用例。
1.父子关系引用
直接输出肯定是报循环错误,Jackson 提供了两个注解
打印结果为:
{"name":"chris","createDate":"2012-04-18"}
{"title":"title","user":{"name":"chris","createDate":"2012-04-18"}}
单向Article 加该注解
打印结果:
{"name":"chris","createDate":"2012-04-18","articles":[{"title":"title"}]}
{"title":"title"}
因为jsonplugin用的是java的内审机制.hibernate会给被管理的pojo加入一个 hibernateLazyInitializer属性,jsonplugin会把hibernateLazyInitializer也拿出来操作,并读取里面一个不能被反射操作的属性就产生了这个异常.
不过我用的是jackson来转json,所以想到了用annotation来排除hibernateLazyInitializer 这个属性
在你的pojo类声明加上:
5.设置字段的过滤
参考(http://www.iteye.com/topic/1122855)
这个加在类级别上, 用法很简单@JsonIgnoreProperties({"property1", "property2"})
动态过滤属性,这个比较麻烦。
有两种方法。
1.使用@JsonFilter注解
使用方法为先给ObjectMapper添加一个filter,然后还要在需要过滤的类上加@JsonFilter("filterName")
注解。
比如说要过滤User 上的name属性,先
Jacksons.json().filter("myFilter", "name").readAsString(user),具体看Jacksons代码。并在User类上加@JsonFilter("myFilter")。
有点不爽的是如果用另外一个没有添加该filter的ObjectMapper解析的话会报错。
如果这个User类已经添加了@JsonFilter("myFilter")注解,但在另外一个地方又要解析它并不想过滤name 属性,那只能是
Jacksons.me().filter("myFilter", ""),然后在读出来。
2.添加混入注解(暂时这么翻译)
定义一个接口或类先, 在该类上添加@JsonIgnoreProperties("name"), 然后在ObjectMapper的配置项上添加混入注解
输出为:
七、附录
1.性能测试
使用
2012-7-26 11:55:18 com.alisoft.nano.bench.listener.SimpleMeasure outputMeasureInfo
信息:
[jackson(重用ObjectMapper)]
avg: 6.31 ms
total: 631.43 ms
tps: 158.37
running: 100 times
in 10 Threads
2012-7-26 11:55:18 com.alisoft.nano.bench.listener.MemoryUsage outputMeasureInfo
信息:
memory-usage: [jackson(重用ObjectMapper)]
3,133.712 Kb
2012-7-26 11:55:19 com.alisoft.nano.bench.listener.SimpleMeasure outputMeasureInfo
信息:
[fastJson]
avg: 4.68 ms
total: 467.97 ms
tps: 213.69
running: 100 times
in 10 Threads
2012-7-26 11:55:19 com.alisoft.nano.bench.listener.MemoryUsage outputMeasureInfo
信息:
memory-usage: [fastJson]
1,195.920 Kb
2012-7-26 11:55:21 com.alisoft.nano.bench.listener.SimpleMeasure outputMeasureInfo
信息:
[Gson]
avg: 13.47 ms
total: 1,347.07 ms
tps: 74.24
running: 100 times
in 10 Threads
2012-7-26 11:55:21 com.alisoft.nano.bench.listener.MemoryUsage outputMeasureInfo
信息:
memory-usage: [Gson]
2,077.808 Kb
2012-7-26 11:55:24 com.alisoft.nano.bench.listener.SimpleMeasure outputMeasureInfo
信息:
[FlexJson]
avg: 17.67 ms
total: 1,766.68 ms
tps: 56.60
running: 100 times
in 10 Threads
2012-7-26 11:55:24 com.alisoft.nano.bench.listener.MemoryUsage outputMeasureInfo
信息:
memory-usage: [FlexJson]
1,529.720 Kb
2012-7-26 11:55:31 com.alisoft.nano.bench.listener.SimpleMeasure outputMeasureInfo
信息:
[json-lib]
avg: 43.12 ms
total: 4,311.97 ms
tps: 23.19
running: 100 times
in 10 Threads
2012-7-26 11:55:31 com.alisoft.nano.bench.listener.MemoryUsage outputMeasureInfo
信息:
memory-usage: [json-lib]
3,098.696 Kb
2012-7-26 11:55:31 com.alisoft.nano.bench.listener.SimpleMeasure outputMeasureInfo
信息:
[StringBuffer]
avg: 4.21 ms
total: 420.93 ms
tps: 237.57
running: 100 times
in 10 Threads
2012-7-26 11:55:31 com.alisoft.nano.bench.listener.MemoryUsage outputMeasureInfo
信息:
memory-usage: [StringBuffer]
1,337.712 Kb
网上测试:http://www.iteye.com/topic/561368
测试结果:
[{"version":null,"orgId":null,"orgPath":null,"depId":10000,"depName":"技术部","depDesc":null,"depLevel":2,"parentId":1,"path":"0.1.10000.","orgType":1,"creatorId":1,"createtime":"2012-07-18 10:37:56","updateId":1,"updatetime":"2012-07-18","sn":null,"chargeIds":"10020","chargeNames":"超级管理员","demension":{"version":null,"orgId":null,"orgPath":null,"demId":1,"demName":"行政维度","demDesc":"行政维度","demType":1,"organizations":[]}},{"version":null,"orgId":null,"orgPath":null,"depId":10002,"depName":"技术2部","depDesc":"技术2部","depLevel":3,"parentId":10000,"path":"0.1.10000.10002.","orgType":2,"creatorId":1,"createtime":"2012-07-18 10:38:47","updateId":1,"updatetime":"2012-07-18","sn":null,"chargeIds":"","chargeNames":"","demension":{"version":null,"orgId":null,"orgPath":null,"demId":1,"demName":"行政维度","demDesc":"行政维度","demType":1,"organizations":[]}},{"version":null,"orgId":null,"orgPath":null,"depId":1,"depName":"宏天软件","depDesc":"宏天软件","depLevel":1,"parentId":0,"path":"0.1.","orgType":1,"creatorId":1,"createtime":"2011-08-29 00:00:00","updateId":1,"updatetime":"2011-08-29","sn":null,"chargeIds":"","chargeNames":"","demension":{"version":null,"orgId":null,"orgPath":null,"demId":1,"demName":"行政维度","demDesc":"行政维度","demType":1,"organizations":[]}},{"version":null,"orgId":null,"orgPath":null,"depId":10001,"depName":"技术1部","depDesc":"技术1部","depLevel":3,"parentId":10000,"path":"0.1.10000.10001.","orgType":2,"creatorId":1,"createtime":"2012-07-18 10:38:15","updateId":1,"updatetime":"2012-07-18","sn":null,"chargeIds":"","chargeNames":"","demension":{"version":null,"orgId":null,"orgPath":null,"demId":1,"demName":"行政维度","demDesc":"行政维度","demType":1,"organizations":[]}}]
[{version=null, orgId=null, orgPath=null, depId=10000, depName=技术部, depDesc=null, depLevel=2, parentId=1, path=0.1.10000., orgType=1, creatorId=1, createtime=2012-07-18 10:37:56, updateId=1, updatetime=2012-07-18, sn=null, chargeIds=10020, chargeNames=超级管理员, demension={version=null, orgId=null, orgPath=null, demId=1, demName=行政维度, demDesc=行政维度, demType=1, organizations=[]}}, {version=null, orgId=null, orgPath=null, depId=10002, depName=技术2部, depDesc=技术2部, depLevel=3, parentId=10000, path=0.1.10000.10002., orgType=2, creatorId=1, createtime=2012-07-18 10:38:47, updateId=1, updatetime=2012-07-18, sn=null, chargeIds=, chargeNames=, demension={version=null, orgId=null, orgPath=null, demId=1, demName=行政维度, demDesc=行政维度, demType=1, organizations=[]}}, {version=null, orgId=null, orgPath=null, depId=1, depName=宏天软件, depDesc=宏天软件, depLevel=1, parentId=0, path=0.1., orgType=1, creatorId=1, createtime=2011-08-29 00:00:00, updateId=1, updatetime=2011-08-29, sn=null, chargeIds=, chargeNames=, demension={version=null, orgId=null, orgPath=null, demId=1, demName=行政维度, demDesc=行政维度, demType=1, organizations=[]}}, {version=null, orgId=null, orgPath=null, depId=10001, depName=技术1部, depDesc=技术1部, depLevel=3, parentId=10000, path=0.1.10000.10001., orgType=2, creatorId=1, createtime=2012-07-18 10:38:15, updateId=1, updatetime=2012-07-18, sn=null, chargeIds=, chargeNames=, demension={version=null, orgId=null, orgPath=null, demId=1, demName=行政维度, demDesc=行政维度, demType=1, organizations=[]}}]