硬盘安装ubuntu 13.04

想通过深度卸载包来回收一点空间,执行了下面的命令,结果导致系统再也不能进入

dpkg --purge `dpkg --get-selections | grep deinstall | cut -f1` 

dpkg –purge时一定要谨慎啊,虽然有些包已经已经标记为deinstall,但事实还是不能卸载的

于是干脆安装ubuntu 13.04

不想刻录光盘,又不想使用u盘,采取硬盘安装方式(非wubi)。
因为需要修改grub.cfg,所以这种安装方式的前提是已经安装有ubuntu旧版本并且还能使用shell。

安装步骤:
1.下载ubuntu 13.04镜像,余是下载了64位的gnome版本。为了简化后面的修改,重命名镜像为ubuntu.iso。镜像推荐放在非目标安装分区的根目录下,例如D:\ubuntu.iso

2.确定ubuntu.iso所在磁盘以及所在分区的序号
只有一个硬盘的情况下,一般sda指示这个硬盘,sda1指示第一个分区。但扩展分区也会占用一个设备文件,并且有些序号没被使用但仍会空缺,所以保险起见,还是在ubuntu能用的时候,使用工具来确定:

sudo gparted

假设结果是/dev/sda5,那么ubuntu.iso所在磁盘/分区就是(hd0,5)

tips:
实在找不到是哪一个设备文件的情况下,可以一个一个的加载尝试(mount或者ntfs-3g命令)

sudo mkdir /mnt/d
sudo ntfs-3g /dev/sda5 /mnt/d
cd /mnt/d
ls

3.修改/boot/grub/grub.cfg(在ubuntu崩溃的情况下,用修复模式进入命令行即可修改),在最后增加:

menuentry "install ubuntu 13.04" --class gnu-linux --class gnu --class os {
        insmod ntfs
        loopback loop (hd0,5)/ubuntu.iso
        linux (loop)/casper/vmlinuz.efi boot=casper iso-scan/filename=/ubuntu.iso locale=en_US.utf8 noprompt noeject splash
        initrd (loop)/casper/initrd.lz
}

注意insmod
fat32/fat16分区 -> fat
NTFS分区 -> ntfs
ext2/ext3/ext4分区 -> ext2

重新启动计算机,grub菜单就会在最后增加一个新的选项。

4.成功启动后,首先卸载iso镜像:

ubuntu-gnome@ubuntu-gnome:~$ sudo umount -l /isodevice

如果启动安装程序后没找到磁盘分区信息,可以尝试卸载dmraid

ubuntu-gnome@ubuntu-gnome:~$ sudo apt-get remove dmraid

启动安装程序后没找到磁盘分区信息是个莫名其妙的BUG,ubuntu各个版本都存在。
新版ubuntu的内核都是3.0以上了,SATA硬盘的AHCI mode(BIOS中设置)是肯定支持的,虽然余切换到ATA模式时BUG也没出现,但推荐使用AHCI模式安装。如果出现这个BUG,在卸载dmraid后还不能解决时,可以尝试修改这个BIOS选项。

===补记===
Gnome版ubuntu 13.04的包升级一定要小心。
在安装好系统后,如果执行

sudo apt-get update
sudo apt-get upgrade

或者按照系统的建议进行包升级,在重启后登录时将会出现Failed to load session "gnome"错误!
原因是Gnome的很多相关包都没进入ubuntu的官方源,这样的升级会导致某些必要包被卸载

推荐的升级方法:

sudo add-apt-repository ppa:gnome3-team/gnome3
sudo apt-get update
sudo apt-get dist-upgrade

补救方法:在修复模式下执行同上命令

4,316 次浏览 | 1 条评论
2013年5月13日 | 归档于 技术
标签: , ,

扩展Google Closure的UI库:支持z-index的Dialog

上头一句命令,J工程由jQuery转到Google closure,于是挨踢民工的命运就是在每天在xxx库/xxx技术的海洋里沉浮

首先拿jQuery+bootstrap写的W工程中的一个功能练手,使用closure重写作为Demo
几点感想:
1. closure API超级罗嗦,jQuery的ID选择功能$('#some-id'),到closure就变成goog.dom.getElement('some-id')
这个还算短的呢,创建一个OK按钮:goog.ui.Dialog.ButtonSet.createOk(),想使用一个常量:goog.ui.Dialog.EventType.SELECT
2. closure很大,功能也很完善,第三方的库到目前为止还没需要
3. closure compiler还没试,但有maven上的plugin,这个超级棒,在打war包时同时完成编译和压缩js
4. closure的UI构造风格和jQuery UI/bootstrap不一样,jQuery UI/bootstrap对设计师更加友好,设计师不懂代码也可以写html和css,有利于分工,而closure UI的html由程序生成,写代码的人还要兼顾设计
5. closure的实现还是写得很明了的,也不亏它的命名又长又臭。从Dialog扩展一个支持z-index的ZindexDialog,余看了一下源码就能猜到是怎么回事。closure的源码比ExtJS的容易看懂

支持z-index的ZindexDialog源码:

/**
 * @author kuyur@kuyur.info
 */

goog.provide('justblog.ui.ZindexDialog');

goog.require('goog.ui.Dialog');

/**
 * @constructor
 * @param {string=} opt_class CSS class name for the dialog element, also used
 *     as a class name prefix for related elements; defaults to modal-dialog.
 * @param {boolean=} opt_useIframeMask Work around windowed controls z-index
 *     issue by using an iframe instead of a div for bg element.
 * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper; see {@link
 *     goog.ui.Component} for semantics.
 * @extends {goog.ui.Dialog}
 */
justblog.ui.ZindexDialog = function(opt_class, opt_useIframeMask, opt_domHelper) {
	goog.base(this, opt_class, opt_useIframeMask, opt_domHelper);
	this.zindex_ = 0;
};
goog.inherits(justblog.ui.ZindexDialog, goog.ui.Dialog);

/**
 * @type {number}
 * @private
 */
justblog.ui.ZindexDialog.prototype.zindex_;

/**
 * Set z-index for the dialog.
 * @param {number} zindex
 */
justblog.ui.ZindexDialog.prototype.setZindex = function(zindex) {
	if (zindex == this.zindex_) {
		return;
	}
	this.zindex_ = zindex;
	this.renderIfNoDom_();

	var dlgEl = this.getElement();
	if (dlgEl) {
		var bgEl = this.getBackgroundElement();
		if (bgEl) {
			bgEl.style.zIndex = zindex;
		}
		dlgEl.style.zIndex = zindex;
	}
};

使用:
自定义模块的调用需要一些工作,可以参考:http://stackoverflow.com/questions/1918996/how-can-i-load-my-own-js-module-with-goog-provide-and-goog-require

在deps.js添加

goog.addDependency('pathToCustomUI/zindexdialog.js', ['justblog.ui.ZindexDialog'], ['goog.ui.Dialog']);

或者制作自己deps.js

demo代码:

goog.require('goog.ui.CustomButton');
goog.require('goog.ui.Dialog');
goog.require('goog.ui.Dialog.ButtonSet');
goog.require('justblog.ui.ZindexDialog');

var dlg = new justblog.ui.ZindexDialog();
dlg.setTitle('用户管理');
dlg.setContent('确定要删除用户吗?');
var buttons = new goog.ui.Dialog.ButtonSet();
buttons.addButton({caption: '取消', key: 'cancel'}, true, true);
buttons.addButton({caption: '确定', key: 'yes'});
dlg.setButtonSet(buttons);
dlg.createDom();
dlg.setZindex(1);
goog.dom.classes.add(dlg.getElement(), 'width-430px');
dlg.addEventListener(goog.ui.Dialog.EventType.SELECT, function(e) {
	if (e.key === "yes") {
		e.preventDefault();
		onYesClick();
	}
});

// ...

dlg.setVisible(true);

当有几个窗口同时处于弹出状态,又希望能控制遮盖关系时,使用setZindex即可设置堆叠顺序。
数字越大越在前。0是内置的默认值,设置成小于0,窗口将会被document覆盖

4,409 次浏览 | 1 条评论
2013年2月21日 | 归档于 技术, 程序

「先有鸡还是先有蛋?」的荒谬之处

先有鸡还是先有蛋?

从古代讨论到今天的蛋疼问题。

首先是进化论上的解答。假定鸡是从恐龙进化(当然实际情况没那么简单,鸡其实是从鸟类分化出来的)
有了进化论之后,蛋也必须是修正为鸡蛋而不是恐龙蛋。更严格一点,鸡蛋到底是「鸡」生的蛋呢还是能成长为「鸡」的蛋呢。
采用后一种「鸡蛋」定义:
按照鸡的定义,恐龙进化成鸡有一个决定性的瞬间(这个真「鸡」的既像恐龙又像鸡的祖先们都还不是鸡),
能够成长为第一个鸡的蛋是第一枚鸡蛋。所以先有鸡蛋才有鸡。

进化论上的解答是如此无趣。

但是我们忽略了古人提出这个问题时的他们认为能难倒对方的要害。
在古人提出问题的时候,没有进化论,因此鸡和蛋实际上是一个无限相互嵌套的队列。
在这个队列中,无论无穷遥远的过去还是无穷遥远的将来,鸡还会是鸡,蛋也还会是蛋,鸡不会突然变成另外的物种。
古人的这个问题实际上还忽略了雌雄,鸡变成了一个单性繁殖的抽象物种。
因此我们应该在古人的提出这个问题的认识基础上来破解这个问题。
(可以参看维基词条

问题其实是荒谬的。荒谬的地方就在于古人将个体的概念偷换成集合。

如果问2大还是1大,当然是2大。
但问偶数大呢还是奇数大呢?显然就非常荒谬了。
我们定义了整数的「大于」和「小于」概念,但没有定义集合的「大于」「小于」概念。
因此两个无穷集合的大小关系是无法确定的。元素个体的大小概念无法直接推广到集合上。

即使非要定义集合的大小关系,两个无穷集合,他们的每一个元素都恰好比对方的大,也恰好比对方的小,怎么比较?比较元素的和?比较平均值?
所以在数学上,集合只有包含/相交/不相交的关系,而没有大于小于的关系。

可以很简单的将先有鸡还是先有蛋问题映射到下面的数学问题:
不妨将鸡映射为奇数。
鸡 → 奇数
蛋 → 偶数
鸡生蛋 → 奇数+1变成偶数
蛋生鸡 → 偶数+1变成奇数
鸡/蛋序列中先有A才于B(A不一定紧邻B,可以隔几代) → 整数序列中,A < B 所以「先有鸡还是先有蛋」问题等价于「奇数大还是偶数大」问题。 正如奇数偶数只有具体个体才能比较大小,集合无从比较大小一样, 「先」这个关系也只能具体到某只鸡某只蛋才能比较,整个鸡集和蛋集是无从比较先后的。 将个体才能适用的概念偷换到集合上,正是「先有鸡还是先有蛋」问题的荒谬所在。 可能还有人要诡辩说, 如果具体到某个鸡和某个蛋,当然知道是鸡先还是蛋先了,但我问的是远古洪荒不知道什么时候开始产生了鸡和蛋,到底是鸡先还是蛋先啊? 如果要问问题呢,就不能既隐含某个立场又否定这个立场。 既然前提要求起点是无穷的开区间,就不能又要求把结果替换成起点是闭区间。 整数序列不知什么时候从负无穷开始产生奇数和偶数,到底是先有奇数还是先有偶数? (-∞,+∞),这个区间先有奇数还是先有偶数?一旦先有奇数,区间就会被截断为[某奇数,+∞)。显然问题不是这样问的。 既然隐含前提鸡/蛋序列是没有起点的,就不能要求答案使得鸡/蛋序列变成有起点。所以问题本省就逻辑矛盾。 一旦你因错就错,例如回答说先有鸡,他们就会问变成这个鸡的蛋呢,将有起点再一次偷换成无起点。 所以古人想不出个所以然来是因为他们一直在偷换概念。

3,783 次浏览 | 1 条评论
2013年1月20日 | 归档于 私语
标签:

充电后电池的质量会发生变化吗?

充电后电池的质量会发生变化吗?

这个和互联网数据的重量都是经常争论的话题。

理论上来说,充电后电池的质量应该会增加,虽然微乎其微。

很多人说充电过程是化学反应,没有发生质能转换过程,因此遵守质量守恒,质量不会发生变化。
这个答案要是放在中学,是正确的。但是正是因为被填鸭式教育洗脑太深,这个认识是肤浅的。

化学反应不严格遵守质量守恒。
严格的议题应该是:化学反应前后,反应物的质量不遵守质量守恒。
(这肯定会和很多人的认识冲突,中学化学教学说前后质量守恒只是从实用角度出发)

能量也是有质量的。
严格的议题应该是:能量会对系统产生质量贡献。
(肯定也会人迷糊)

首先从非化学的核反应说起。
大家都知道核裂变/核聚变/湮灭都会造成质量亏损,而且这个质量亏损是可以量度的。
亏损部分的质量转化为能量,整个过程遵守质能守恒。
一个不和外界发生质能交换的封闭系统,系统内部发生了核裂变,或者更极端一点,如正负电子湮灭,
系统内部的物质质量是减少了,但整个系统放到天平上称,就会发生变化了?
显然封闭系统的整体质量不会发生变化。封闭系统内的能量(γ光子)对系统的质量贡献弥补了物质质量减少的部分。
(具体可以参阅光子维基词条 http://zh.wikipedia.org/wiki/%E5%85%89%E5%AD%90)
光子具有动质量。

很多人不理解化学能的真正本质。
化学能释放的本质是伴随化学键重组电子从高能态轨道向低能态轨道跃迁产生的光子辐射。
化学能存储是其逆过程。电子在高能态轨道时的系统质量比在低能态轨道时大。

当2个氢分子和一个氧分子发生反应生成2个水分子,氢原子核还是那些氢原子核,氧原子核也还是那些氧原子核,
电子既不增多,也没有减少,只是电子的能态已经发生了变化,所在轨道也和反应前大不相同。
但由于辐射出光子,这个非封闭系统的质量已经减轻。这个质量变化数值非常非常微小,只存在于理论上,真实设备测不出来。

因此,化学反应前后,反应物的质量不遵守质量守恒。

说到底,物质的质量不单止是原子核和电子静质量的简单叠加,还包括原子核和核外电子的能量关系所产生的质量。
(同样道理,原子核质量不是质子和中子质量的简单叠加,质子/中子质量也不是夸克质量的简单叠加。越靠近物质本源,度量上质量不足就越可观)

回到电池充电。
电池充电后电池内部的电子数量并不会增加!(认为电子数量增加的认识是错误的,电池不是电容器)
电池充电后电池内部的原子核还是那些原子核,电子数量也没变化,但因为电子的能级发生了变化(具体表现在化学键上),
电池的蕴涵能量增加,理论上电池增加了这份能量对应的质量。具体可由质能方程计算出来。

一个2000mAh的1.2v电池,可以2A电流,1.2v电压,持续放电一小时,
因此简单的可以认为充满电后电池增加能量:2×1.2×3600=8640(J)
对应的质量增加:8640/(3×10^8)^2 = (8640/9)x10^(-16) = 9.6×10^(-14)(kg)

3,138 次浏览 | 没有评论
2013年1月17日 | 归档于 私语
标签:

再次修改skydrive-gae获取skydrive外链

与天斗,与地斗,与微软斗,其乐无穷
微软再一次修改了skydrive的文件获取逻辑,上一个版本的skydrive-gae终于失效

仔细分析了新版的文件重定向流程
比如访问URL:http://cid-e29fae4e10288b80.office.live.com/self.aspx/Public/PRMY-0018/PRMY-0018.rar
Response的Header的Location:https://skydrive.live.com/self.aspx/Public/PRMY-0018/PRMY-0018.rar?cid=e29fae4e10288b80&sc=documents

于是浏览器重定向访问:https://skydrive.live.com/self.aspx/Public/PRMY-0018/PRMY-0018.rar?cid=e29fae4e10288b80&sc=documents
Response的Header的Location:https://skydrive.live.com/?cid=e29fae4e10288b80&id=E29FAE4E10288B80%21300&sc=documents

浏览器再次重定向至https://skydrive.live.com/?cid=e29fae4e10288b80&id=E29FAE4E10288B80%21300&sc=documents
重定向到此为止

这个地方是关键:

E29FAE4E10288B80%21300

拿去喂skydrive.live.com/download.aspx就能获取动态的文件地址

http://skydrive.live.com/download.aspx?cid=E29FAE4E10288B80&resid=E29FAE4E10288B80%21300&canary=

于是关键地方就是获取所谓的resid,要害在第二次重定向

温习了下python,现学现用

# -*- coding: utf-8 -*-
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
import urllib2, httplib
import re

def getcid(link):
    pos = link.find('.office.live.com')
    return link[4:pos]

def to_new_type_link(link):
    pos = link.find('.office.live.com')
    cid = link[4:pos]
    uri = link[pos+len('.office.live.com'):]
    new_link = 'skydrive.live.com' + uri + '?cid=' + cid# + '&sc=documents'
    return new_link

def get_real_link(static_url):    
    curl = "http://" + static_url
    request = urllib2.Request(curl)
    opener = urllib2.build_opener()
    response = opener.open(request)
    realurl = response.geturl()
    return realurl

def get_dynamic_download_link(real_url, cid):
    pos = real_url.find(cid) + len(cid) + len('&id=')
    resid = real_url[pos:]
    return 'http://skydrive.live.com/download.aspx?cid=' + cid.upper() + '&resid=' + resid + '&canary='

def replace_html_code(old_link):
    new_link = old_link.replace('\\/','/')
    new_link = new_link.replace('https://','http://')
    return new_link

class MainPage(webapp.RequestHandler):
    def get(self):
        old_type_link = self.request.path[1:]
        cid = getcid(old_type_link)
        new_type_link = to_new_type_link(old_type_link)
        real_link = get_real_link(new_type_link)
        self.response.headers['Content-Type'] = 'text/plain'
        self.redirect(replace_html_code(get_dynamic_download_link(real_link, cid)))
        
class IndexPage(webapp.RequestHandler):
    def get(self):
        self.response.out.write('''<html>
<head>
<title>
skydrie-gae
</title>
<meta content="A Google App Engine application for redirect the skydrive link" name="description">
<meta content="skydrive, skydirve-gae, gae, google app engin," name="keywords">
<meta name="google-site-verification" content="MRuE10bVaHltuPvjYFH0PGfvJAuCZ2FScfAviXiKDFE" />
<script type="text/javascript">

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-4981483-8']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

</script>
</head>
<body>
This another Google App Engine application!<br>
You can use it redirect the skydrive link.<br>
It's very simple:copy your skydrive link after the HOST,Then it's work.<br>
<br>
For Example:<br>
your skydrive link is <br>
http://cid-7978ee0a34b7f662.office.live.com/self.aspx/Public/EMS/2010/0627.mp3<br>
Then the static access link should be <br>
http://skydrive-gae.appspot.com/cid-7978ee0a34b7f662.office.live.com/self.aspx/Public/EMS/2010/0627.mp3<br>

remember remove the skydrive "http://"<br>

<br>
Thanks to <img src="http://msl.appspot.com/static/images/gmail.png"><br>
<br>
Modified by <img src="https://kuyur.info/blog/uploads/2010/03/mail.png"><br>
</body>
</html>''')
     
application = webapp.WSGIApplication(
                                     [('/', IndexPage),
                                     ('/index.html', IndexPage),
                                     ('/index.htm', IndexPage),
                                     ('/cid.*', MainPage),
                                     #(r'/http\*',MainPage),
                                     ],
                                     debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

skydrive-gae下载(python2.5):
skydrive-gae

才发现SDUpload失效了,要Google的官方SDK才能上传

=====追记======
其实python2.5版用得好好的,但每次登录appengine,都吵着要你升级到python2.7和新版的DataStore
skydrive-gae并不需要读写DataStore,至于升级到python2.7,稍微修改一下代码即可。

# -*- coding: utf-8 -*-
import webapp2
import urllib2, httplib
import re

def getcid(link):
    pos = link.find('.office.live.com')
    return link[4:pos]

def to_new_type_link(link):
    pos = link.find('.office.live.com')
    cid = link[4:pos]
    uri = link[pos+len('.office.live.com'):]
    new_link = 'skydrive.live.com' + uri + '?cid=' + cid# + '&sc=documents'
    return new_link

def get_real_link(static_url):    
    curl = "http://" + static_url
    request = urllib2.Request(curl)
    opener = urllib2.build_opener()
    response = opener.open(request)
    realurl = response.geturl()
    return realurl

def get_dynamic_download_link(real_url, cid):
    pos = real_url.find(cid) + len(cid) + len('&id=')
    resid = real_url[pos:]
    return 'http://skydrive.live.com/download.aspx?cid=' + cid.upper() + '&resid=' + resid + '&canary='

def replace_html_code(old_link):
    new_link = old_link.replace('\\/','/')
    new_link = new_link.replace('https://','http://')
    return new_link

class MainPage(webapp2.RequestHandler):
    def get(self):
        old_type_link = self.request.path[1:]
        cid = getcid(old_type_link)
        new_type_link = to_new_type_link(old_type_link)
        real_link = get_real_link(new_type_link)
        self.response.headers['Content-Type'] = 'text/plain'
        self.redirect(replace_html_code(get_dynamic_download_link(real_link, cid)))
        
class IndexPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('''<html>
<head>
<title>
skydrie-gae
</title>
<meta content="A Google App Engine application for redirect the skydrive link" name="description">
<meta content="skydrive, skydirve-gae, gae, google app engin," name="keywords">
<meta name="google-site-verification" content="MRuE10bVaHltuPvjYFH0PGfvJAuCZ2FScfAviXiKDFE" />
<script type="text/javascript">

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-4981483-8']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

</script>
</head>
<body>
This another Google App Engine application!<br>
You can use it redirect the skydrive link.<br>
It's very simple:copy your skydrive link after the HOST,Then it's work.<br>
<br>
For Example:<br>
your skydrive link is <br>
http://cid-7978ee0a34b7f662.office.live.com/self.aspx/Public/EMS/2010/0627.mp3<br>
Then the static access link should be <br>
http://skydrive-gae.appspot.com/cid-7978ee0a34b7f662.office.live.com/self.aspx/Public/EMS/2010/0627.mp3<br>

remember remove the skydrive "http://"<br>

<br>
Thanks to <img src="http://msl.appspot.com/static/images/gmail.png"><br>
<br>
Modified by <img src="https://kuyur.info/blog/uploads/2010/03/mail.png"><br>
</body>
</html>''')
     
app = webapp2.WSGIApplication(
                                     [('/', IndexPage),
                                     ('/index.html', IndexPage),
                                     ('/index.htm', IndexPage),
                                     ('/cid.*', MainPage),
                                     #(r'/http\*',MainPage),
                                     ],
                                     debug=True)

app.yaml也要修改一下:

application: your-app-id
version: 9
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /.*
  script: index.app

skydrive-gae下载(python2.7):
skydrive-gae

4,146 次浏览 | 没有评论
2013年1月14日 | 归档于 技术, 程序

改良jQuery File Upload插件以及实现HTML5上传插件

在实现文件上传时刚开始用的是HTML5的FormData,简单优雅又快捷。但老大说不行,IE不支持不行,好想抽他。

jQuery的上传插件很多,有用HTML5的也有用iFrame的。
内部另一项目用的是lagos(http://lagoscript.org,应该是个泥轰精)开发的版本,拿来试用一下,发现居然没有处理失败的回调函数

原来的代码

/*
 * jQuery.upload v1.0.2
 *
 * Copyright (c) 2010 lagos
 * Dual licensed under the MIT and GPL licenses.
 *
 * http://lagoscript.org
 */
(function($) {

	var uuid = 0;

	$.fn.upload = function(url, data, callback, type) {
		var self = this, inputs, checkbox, checked,
			iframeName = 'jquery_upload' + ++uuid,
			iframe = $('<iframe name="' + iframeName + '" style="position:absolute;top:-9999px" />').appendTo('body'),
			form = '<form target="' + iframeName + '" method="post" enctype="multipart/form-data" />';

		if ($.isFunction(data)) {
			type = callback;
			callback = data;
			data = {};
		}

		checkbox = $('input:checkbox', this);
		checked = $('input:checked', this);
		form = self.wrapAll(form).parent('form').attr('action', url);

		// Make sure radios and checkboxes keep original values
		// (IE resets checkd attributes when appending)
		checkbox.removeAttr('checked');
		checked.attr('checked', true);

		inputs = createInputs(data);
		inputs = inputs ? $(inputs).appendTo(form) : null;

		form.submit(function() {
			iframe.load(function() {
				var data = handleData(this, type),
					checked = $('input:checked', self);

				form.after(self).remove();
				checkbox.removeAttr('checked');
				checked.attr('checked', true);
				if (inputs) {
					inputs.remove();
				}

				setTimeout(function() {
					iframe.remove();
					if (type === 'script') {
						$.globalEval(data);
					}
					if (callback) {
						callback.call(self, data);
					}
				}, 0);
			});
		}).submit();

		return this;
	};

	function createInputs(data) {
		return $.map(param(data), function(param) {
			return '<input type="hidden" name="' + param.name + '" value="' + param.value + '"/>';
		}).join('');
	}

	function param(data) {
		if ($.isArray(data)) {
			return data;
		}
		var params = [];

		function add(name, value) {
			params.push({name:name, value:value});
		}

		if (typeof data === 'object') {
			$.each(data, function(name) {
				if ($.isArray(this)) {
					$.each(this, function() {
						add(name, this);
					});
				} else {
					add(name, $.isFunction(this) ? this() : this);
				}
			});
		} else if (typeof data === 'string') {
			$.each(data.split('&'), function() {
				var param = $.map(this.split('='), function(v) {
					return decodeURIComponent(v.replace(/\+/g, ' '));
				});

				add(param[0], param[1]);
			});
		}

		return params;
	}

	function handleData(iframe, type) {
		var data, contents = $(iframe).contents().get(0);

		if ($.isXMLDoc(contents) || contents.XMLDocument) {
			return contents.XMLDocument || contents;
		}
		data = $(contents).find('body').html();

		switch (type) {
			case 'xml':
				data = parseXml(data);
				break;
			case 'json':
				data = window.eval('(' + data + ')');
				break;
		}
		return data;
	}

	function parseXml(text) {
		if (window.DOMParser) {
			return new DOMParser().parseFromString(text, 'application/xml');
		} else {
			var xml = new ActiveXObject('Microsoft.XMLDOM');
			xml.async = false;
			xml.loadXML(text);
			return xml;
		}
	}

})(jQuery);

改造了一下,主要是添加失败处理。
iFrame的onload事件一旦发生,说明response已经返回,所以在解析得到响应数据后,可以立刻清除iFrame。
iFrame的HTTP状态码无法判读,因此只能从响应数据判断上传成功还是失败。
服务器端返回的响应应当遵守下面的规则:
1.如果上传失败,HTTP状态码应为500,具体错误信息放在ResponseBody。这是为了兼容普通的XHR请求。
2.如果上传成功,HTTP状态码应为200,返回信息为json格式。
如果上传失败,返回的HTTP响应状态码为200,ResponseBody中放false(json)或者错误信息的字符串(非json),这个修改版的上传插件仍然能用。但服务器端如果要适配XHR文件上传,XHR的失败处理将变得非常丑陋。

/*
 * jQuery.upload v1.0.2
 *
 * Copyright (c) 2010 lagos
 * Dual licensed under the MIT and GPL licenses.
 * http://lagoscript.org
 *
 * modified by kuyur(https://kuyur.info)
 */
(function($) {

    var uuid = 0;

    /*
     * expectedType: only allow JSON now.
     */
    $.fn.upload = function(url, expectedType, onSuccess, onFailure, sendingData) {
        var self = this, inputs, checkbox, checked,
            iframeName = 'jquery_upload' + ++uuid,
            iframe = $('<iframe name="' + iframeName + '" style="display:none;visibility:hidden;height:0px;" />').appendTo('body'),
            form = '<form target="' + iframeName + '" method="post" enctype="multipart/form-data" />';

        checkbox = $('input:checkbox', this);
        checked = $('input:checked', this);
        form = self.wrapAll(form).parent('form').attr('action', url);

        // Make sure radios and checkboxes keep original values
        // (IE resets checkd attributes when appending)
        checkbox.removeAttr('checked');
        checked.attr('checked', true);

        if (typeof sendingData !== "object" && typeof sendingData !== "string") {
            sendingData = {};
        }
        inputs = createInputs(sendingData);
        inputs = inputs ? $(inputs).appendTo(form) : null;

        form.submit(function() {
            iframe.load(function() {
                var iframeDocument = $(this).contents().get(0),
                    response = $(iframeDocument).text(),
                    data = handleResponse(response, expectedType),
                    checked = $('input:checked', self);

                form.after(self).remove();
                checkbox.removeAttr('checked');
                checked.attr('checked', true);
                if (inputs) {
                    inputs.remove();
                }

                setTimeout(function() {
                    iframe.remove();
                    if (data) {
                        onSuccess.call(self, data);
                    } else {
                        onFailure.call(self, response);
                    }
                }, 0);
            });
        }).submit();

        return this;
    };

    function createInputs(data) {
        return $.map(param(data), function(param) {
            return '<input type="hidden" name="' + param.name + '" value="' + param.value + '"/>';
        }).join('');
    }

    function param(data) {
        if ($.isArray(data)) {
            return data;
        }
        var params = [];

        function add(name, value) {
            params.push({name:name, value:value});
        }

        if (typeof data === 'object') {
            $.each(data, function(name) {
                if ($.isArray(this)) {
                    $.each(this, function() {
                        add(name, this);
                    });
                } else {
                    add(name, $.isFunction(this) ? this() : this);
                }
            });
        } else if (typeof data === 'string') {
            $.each(data.split('&'), function() {
                var param = $.map(this.split('='), function(v) {
                    return decodeURIComponent(v.replace(/\+/g, ' '));
                });

                add(param[0], param[1]);
            });
        }

        return params;
    }

    function handleResponse(response, expectedType) {
        if (typeof response !== 'string') {
            return false;
        }
        try {
            return jQuery.parseJSON(response);
        } catch (e) {
            return false;
        }
    }

})(jQuery);

用法:

$('#input_id_or_.wrap_class').upload('upload_url', 'json', function(responseObj){}, function(errorMsg){})

expectedType目前只支持json,余甚至想从参数列中去掉它了,对余来说,支持json就足够。处理成功的回调函数的参数是解析后的javascript对象,处理失败的回调函数的参数是失败信息的原始字符串,这两点一定要注意。

具体例子(注意input的name属性一定要和服务器端一致):

<div>
    <input type="file" id="upload-file" class="form-wrap" name="file">
    <span id="upload-status" class="form-wrap">Please select a file.</span>
</div>
<div class="main">
    <div class="left">Name:</div>
    <div class="right" id="result-name"></div>
    <div class="clear"></div>
    <div class="left">Type:</div>
    <div class="right" id="result-type"></div>
    <div class="clear"></div>
    <div class="left">Size:</div>
    <div class="right" id="result-size"></div>
    <div class="clear"></div>
</div>
$(function(){
    $('#upload-file').change(function(){
        if (!$(this).val()) {
            $('#upload-status').text('Please select a file.');
            $('#result-name').text('');
            $('#result-type').text('');
            $('#result-size').text('');
            return;
        }
        $('#upload-status').html('<img src="wait.gif"> Uploading...');
        $('.form-wrap').upload('upload_file.php', 'json',
            function(responseObj){
                $('#upload-status').text('Upload finished.');
                $('#result-name').text(responseObj.Name);
                $('#result-type').text(responseObj.Type);
                $('#result-size').text(responseObj.Size);
            }, function(errorMsg){
                $('#upload-status').text('Upload failed. Message from server: ' + errorMsg);
                $('#result-name').text('');
                $('#result-type').text('');
                $('#result-size').text('');
            }
        );
    });
});

余写的HTML5上传插件,很简单的代码。使用了FormData对象以及input的files属性,支持IE10/Chrome/Firefox。
响应仅支持json格式,支持多文件上传(没测试),支持处理上传进度。onSuccess和onFailure的参数同上,onProgress的参数为浏览器内置对象event

/*
 * html5 file upload.
 * by kuyur (https://kuyur.info)
 */
(function($) {
    /*
     * multiple-files upload supported.
     * Default file-field will be "file{n} if they are not set."
     */
    $.fn.upload5 = function(url, onSuccess, onFailure, onProgress) {
        if (typeof FormData === 'undefined') {
            return;
        }
        var inputs = this,
            form = new FormData(),
            count = 0;
        inputs.each(function(index) {
            var el = this;
            if (el.files && el.files.length > 0) {
                form.append(el.name || 'file'+index, el.files[0]);
                count++;
            }
        });
        if (count > 0) {
            var xhr = new XMLHttpRequest();
            xhr.open("post", url, true);
            if (typeof onProgress === "function") {
                xhr.upload.onprogress = onProgress;
            }
            xhr.onreadystatechange = function() {
                var me = this;
                if (me.readyState == 4) {
                    if (me.status >= 200 && me.status < 300) {
                        onSuccess($.parseJSON(me.responseText));
                    } else {
                        onFailure(me.responseText);
                    }
                }
            };
            xhr.send(form);
        }
    };
})(jQuery);

例子(注意input的name属性一定要和服务器端一致):

<div>
    <input type="file" id="upload-file-html5" name="file">
    <span id="upload-status-html5">Please select a file.</span>
</div>
<div class="main">
    <div class="left">Upload status:</div>
    <div class="right" id="result-uploadstatus-html5"></div>
    <div class="clear"></div>
    <div class="left">Name:</div>
    <div class="right" id="result-name-html5"></div>
    <div class="clear"></div>
    <div class="left">Type:</div>
    <div class="right" id="result-type-html5"></div>
    <div class="clear"></div>
    <div class="left">Size:</div>
    <div class="right" id="result-size-html5"></div>
    <div class="clear"></div>
</div>
$(function(){
    $('#upload-file-html5').change(function() {
        if (!$(this).val()) {
            $('#upload-status-html5').text('Please select a file.');
            $('#result-uploadstatus-html5').text('');
            $('#result-name-html5').text('');
            $('#result-type-html5').text('');
            $('#result-size-html5').text('');
            return;
        }
        $('#upload-status-html5').html('<img src="wait.gif"> Uploading...');
        $('#upload-file-html5').upload5('upload_file.php',
            function(responseObj){
                $('#upload-status-html5').text('Upload finished.');
                $('#result-name-html5').text(responseObj.Name);
                $('#result-type-html5').text(responseObj.Type);
                $('#result-size-html5').text(responseObj.Size);
            }, function(errorMsg){
                $('#upload-status-html5').text('Upload failed. Message from server: ' + errorMsg);
                $('#result-uploadstatus-html5').text('');
                $('#result-name-html5').text('');
                $('#result-type-html5').text('');
                $('#result-size-html5').text('');
            }, function(event) {
                if (event.lengthComputable) {
                    var completed = (event.loaded / event.total * 100 | 0) + "%";
                    $('#result-uploadstatus-html5').text(completed);
                }
            }
        );
    });
});

用PHP写的一个简单服务器端:

<?php
/*
 * A simple file server demo.
 * by kuyur(https://kuyur.info)
 */
$error_types = array(
1=>'The uploaded file exceeds the upload_max_filesize directive in php.ini.',
'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
'The uploaded file was only partially uploaded.',
'No file was uploaded.',
6=>'Missing a temporary folder.',
'Failed to write file to disk.',
'A PHP extension stopped the file upload.'
); 

if ($_SERVER['REQUEST_METHOD'] != 'POST') {
    header("HTTP/1.1 500 Internal Server Error");
    die('Error: Only post method allowed.');
}
if (empty($_FILES["file"])) {
    header("HTTP/1.1 500 Internal Server Error");
    die('Error: File field should name as "file". Or you may need to check post_max_size in php.ini.');
}
if ($_FILES["file"]["error"] > 0) {
    header("HTTP/1.1 500 Internal Server Error");
    die("Error: " . $error_types[$_FILES['file']['error']]);
} else {
    echo '{"Name": "' . $_FILES["file"]["name"] . '", "Type": "' . $_FILES["file"]["type"] . '", "Size": "' . number_format($_FILES["file"]["size"] / 1024, 1) . 'Kb"}';
}
?>

源码下载(压缩包内的jQuery的版本有点老,替换成新版应该也没问题)jquery-upload

5,752 次浏览 | 没有评论
2012年12月27日 | 归档于 程序

派生类嵌套父类或自包含的数据模型的JSON的反序列化

派生类嵌套父类的情况,Jackson只能做到有限支持。

考虑父类:

package info.kuyur.jsondemo.models;

public class Parent {

	private String name;

	public Parent() {
		super();
	}

	public Parent(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Parent [name=" + name + "]";
	}
}

子类:

package info.kuyur.jsondemo.models;

public class Child extends Parent {

	private Integer age;
	private Parent parent;

	public Child() {
		super();
	}

	public Child(Integer age, Parent parent) {
		super();
		this.age = age;
		this.parent = parent;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public Parent getParent() {
		return parent;
	}

	public void setParent(Parent parent) {
		this.parent = parent;
	}

	@Override
	public String toString() {
		return "Child [age=" + age + ", parent=" + parent + ", name="
				+ getName() + "]";
	}
}

对于json:{"age":15,"parent":{"name":"parent-name"},"name":"child-name"},能反序列化

如果构建这么一个对象:

	@Test
	public void testToJsonString3() {
		Parent grandFather = new Parent("grand-father-name");
		Child father = new Child(35, grandFather);
		father.setName("father-name");
		Child child = new Child(15, father);
		child.setName("grand-child-name");
		String jsonString = Util.toJsonString(child, Child.class);
		System.out.println(jsonString);
	}

得到的json:{"age":15,"parent":{"age":35,"parent":{"name":"grand-father-name"},"name":"father-name"},"name":"grand-child-name"},则无法反序列化,因为Jackson并不知道parent实际上应该用Child的构造函数来构建,Jackson仍然会用Parent的构造函数来构建parent,由于字段"age":35无法忽略,于是抛出异常。

非继承的自包含,比如单向链表的节点,反序列化则没有问题。

package info.kuyur.jsondemo.models;

public class LinkedNode {

	private String nodeName;
	private LinkedNode nextNode;

	public LinkedNode() {
		super();
	}

	public String getNodeName() {
		return nodeName;
	}

	public void setNodeName(String nodeName) {
		this.nodeName = nodeName;
	}

	public LinkedNode getNextNode() {
		return nextNode;
	}

	public void setNextNode(LinkedNode nextNode) {
		this.nextNode = nextNode;
	}

	@Override
	public String toString() {
		return "LinkedNode [nodeName=" + nodeName + ", nextNode=" + nextNode
				+ "]";
	}
}

json:{"nodeName":"node1","nextNode":{"nodeName":"node2","nextNode":{"nodeName":"node3","nextNode":null}}}能反序列化成功。

归结到一点,还是要为Jackson提供足够的型信息。
派生类嵌套父类会导致歧义性,Jackson只会调用基类的构造函数构建成员,所以派生类嵌套父类不是值得推荐的数据组织方法。

5,673 次浏览 | 1 条评论
2012年12月19日 | 归档于 技术, 程序

Java泛型和JSON的反序列化(下)

上篇:https://kuyur.info/blog/archives/2729
中篇:https://kuyur.info/blog/archives/2772

中篇介绍了如何构造自己的参数化类型(ParameterizedType),但一步步构造还是嫌麻烦。
本篇介绍Jackson提供的【型】工具。

TypeReference是一个抽象类,但在使用它时却不需要实现任何方法。
例如获取Triple<User, Archive, Comment>的型:

Type tripleGenericType = new TypeReference<Triple<User, Archive, Comment>>(){}.getType();

TypeReference的源码:

package org.codehaus.jackson.type;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public abstract class TypeReference<T>
    implements Comparable<TypeReference<T>>
{
    final Type _type;

    protected TypeReference()
    {
        Type superClass = getClass().getGenericSuperclass();
        if (superClass instanceof Class<?>) { // sanity check, should never happen
            throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
        }
        /* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect
         *   it is possible to make it fail?
         *   But let's deal with specifc
         *   case when we know an actual use case, and thereby suitable
         *   work arounds for valid case(s) and/or error to throw
         *   on invalid one(s).
         */
        _type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
    }

    public Type getType() { return _type; }

    /**
     * The only reason we define this method (and require implementation
     * of <code>Comparable</code>) is to prevent constructing a
     * reference without type information.
     */
    public int compareTo(TypeReference<T> o) {
        // just need an implementation, not a good one... hence:
        return 0;
    }
}

TypeReference的构造函数用protected修饰,因此只能够由子类调用。

new TypeReference<Triple<User, Archive, Comment>>(){};

上面的代码构造了一个派生自TypeReference<Triple<User, Archive, Comment>>的匿名子类实例,
匿名子类在构造时,通过反射获取父类的参数化类型,也即TypeReference<Triple<User, Archive, Comment>>型,
再通过ParameterizedType的接口getActualTypeArguments(),获得Triple<User, Archive, Comment>型
(ParameterizedType的详细说明见中篇)

getGenericSuperclass()和getSuperclass()都是Class提供的方法,
如果匿名子类调用getSuperclass(),获得的将只是TypeReference.class,而不会包含泛型信息。

Type tripleGenericType = new TypeReference<Triple<User, Archive, Comment>>(){}.getType();实际上是下面代码的语法糖:

TypeReference<Triple<User, Archive, Comment>> object = new TypeReference<Triple<User, Archive, Comment>>(){};
Type superType = object.getClass().getGenericSuperclass();
Type tripleGenericType = ((ParameterizedType)superType).getActualTypeArguments()[0];

了解到getGenericSuperclass()可以获取参数化类型,于是我们可以继续玩一下:

	@Test
	public void testGetGenericSuperclass2() throws UnsupportedEncodingException {
		class ExtTriple extends Triple<User, Archive, Comment> {}
		Type tripleGenericType = ExtTriple.class.getGenericSuperclass();
		System.out.println(tripleGenericType);

		String jsonString = "{\"second\":{\"ID\":1000,\"标题\":\"18岁的那天\",\"正文\":\"18岁那天的早上...\",\"作者\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}},\"third\":{\"作者\":{\"mailAddress\":\"qian.qiu@wandai.com\",\"name\":\"铅球万袋\"},\"回复\":\"骚年よ、大志を抱け\"},\"first\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}}";
		ByteArrayInputStream bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
		Triple<User, Archive, Comment> triple = Util.readJsonFromStream(bais, tripleGenericType);
		System.out.println(triple.getFirst());
		System.out.println(triple.getSecond());
		System.out.println(triple.getThird());
	}

到这里Java泛型与JSON反序列化的冒险之旅已经接近尾声。

ObjectMapper重载了n+个readValue接口,其中有一组接口支持TypeReference。(但没有readValue接口直接支持Type。)
为Util添加一个静态转换函数:

public class Util {
	...
	public static <T> T readJsonFromString(String jsonString, TypeReference<T> typeRef) {
		ObjectMapper mapper = new ObjectMapper();
		try {
			return mapper.readValue(jsonString, typeRef);
		} catch (JsonParseException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		} catch (JsonMappingException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		} catch (IOException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		}
	}
|

使用很简单,构造一个TypeReference的匿名子类的对象作为参数传入:

	@Test
	public void testReadJsonFromString11() {
		String jsonString = "{\"1\" : {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"2\" : {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}}";
		Map<Integer, User> users = Util.readJsonFromString(jsonString, new TypeReference<Map<Integer, User>>(){});
		assertEquals(users.size(), 2);
		User user1 = users.get(Integer.valueOf(1));
		System.out.println(user1);
		User user2 = users.get(Integer.valueOf(2));
		System.out.println(user2);
	}

Jackson还定义了自己的【型】数据结构:JavaType,readValue接口同样支持这种【型】数据结构。

public <T> T readValue(String content, JavaType valueType)

如果阅读ObjectMapper的代码,就会发现【型】或者TypeReference最后都会转换为JavaType。
工厂类TypeFactory提供了静态转换方法。
于是继续为Util添加一个静态转换函数:

public class Util {
	...
	public static <T> T readJsonFromString(String jsonString, Type genericType) {
		ObjectMapper mapper = new ObjectMapper();
		try {
			return mapper.readValue(jsonString, TypeFactory.type(genericType));
		} catch (JsonParseException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		} catch (JsonMappingException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		} catch (IOException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		}
	}
|

使用:
(注意使用默认构造函数创建的ObjectMapper对象不支持JAXBAnnotation式的标签,必须为Archive添加JacksonAnnotation式的标签)

	@Test
	public void testReadJsonFromString18() throws UnsupportedEncodingException {
		class ExtTriple extends Triple<User, Archive, Comment> {}
		Type tripleGenericType = ExtTriple.class.getGenericSuperclass();

		String jsonString = "{\"second\":{\"ID\":1000,\"标题\":\"18岁的那天\",\"正文\":\"18岁那天的早上...\",\"作者\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}},\"third\":{\"作者\":{\"mailAddress\":\"qian.qiu@wandai.com\",\"name\":\"铅球万袋\"},\"回复\":\"骚年よ、大志を抱け\"},\"first\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}}";
		Triple<User, Archive, Comment> triple = Util.readJsonFromString(jsonString, tripleGenericType);
		System.out.println(triple.getFirst());
		System.out.println(triple.getSecond());
		System.out.println(triple.getThird());
	}

最后提供源码下载,内含一个不支持多线程的HttpClient Demo:jsondemo

Java泛型和JSON的反序列化(中)

Java泛型和JSON的反序列化(中)

上篇:https://kuyur.info/blog/archives/2729

上篇介绍了ObjectMapper的readValue(String, Class<T>)接口,这个接口的源虽然是字符串,但ObjectMapper实际上重载了众多的readValue,源可以是二进制数组(byte[])/文件(File)/资源定位符(URL)/输入流(InputStream)等,json源并不是关注重点。

Jackson专门为加注JAXBAnnotation标签的数据模型准备了JacksonJaxbJsonProvider。默认的构造函数支持JAXBAnnotation和JacksonAnnotation两种标签。但它的readFrom接口仅支持从InputStream读取json。

写成一个工具类的方法:

	@SuppressWarnings("unchecked")
	public static <T> T readJsonFromStream(InputStream is, Type genericType) {
		JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
		try {
			return (T)provider.readFrom(Object.class, genericType, new Annotation[0], MediaType.APPLICATION_JSON_TYPE, null, is);
		} catch (Exception e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		}
	}

readFrom接口参数很繁杂,但对于一般转换而言,仅需要把【型】(Type genericType)和输入流(InputStream is)交给它即可。
由于Type是一切【型】的基础,因此readJsonFromStream(InputStream, Type)的转换能力要比readJsonFromString(String, Class<T>)强。

测试一下转换能力

Comment的一条json,JacksonAnnotation标签,反序列化毫无压力
{"作者": {"name": "铅球万袋", "mailAddress": "qian.qiu@wandai.com"}, "回复": "骚年よ、大志を抱け"}

	@Test
	public void testReadJsonFromString15() throws UnsupportedEncodingException {
		String jsonString = "{\"作者\": {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"回复\": \"骚年よ、大志を抱け\"}";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			Comment comment = Util.readJsonFromStream(bais, Comment.class);
			System.out.println(comment);
			assertEquals(comment.getContent(), "骚年よ、大志を抱け");
			assertEquals(comment.getAuthor().getName(), "铅球万袋");
			assertEquals(comment.getAuthor().getMailAddress(), "qian.qiu@wandai.com");
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

Stream遵循谁创建谁关闭的原则。ByteArrayInputStream事实上不需要关闭。

Archive的一条json,JAXBAnnotation标签,反序列化通过
{"ID": "1000", "标题": "18岁的那天", "正文": "18岁那天的早上...", "作者": {"name": "一桶浆糊", "mailAddress": "yi.tong@jianghu.com"}}

	@Test
	public void testReadJsonFromString3() throws UnsupportedEncodingException {
		String jsonString = "{\"ID\": \"1000\", \"标题\": \"18岁的那天\", \"正文\": \"18岁那天的早上...\", \"作者\": {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}}";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			Archive archive = Util.readJsonFromStream(bais, Archive.class);
			System.out.println(archive);
			assertEquals(archive.getBlogId(), 1000);
			assertEquals(archive.getTitle(), "18岁的那天");
			assertEquals(archive.getText(), "18岁那天的早上...");
			assertNotNull(archive.getAuthor());
			User author = archive.getAuthor();
			assertEquals(author.getName(), "一桶浆糊");
			assertEquals(author.getMailAddress(), "yi.tong@jianghu.com");
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

再来测试泛型容器

User数组,我们希望转换为List<User>
[{"name": "铅球万袋", "mailAddress": "qian.qiu@wandai.com"}, {"name": "一桶浆糊", "mailAddress": "yi.tong@jianghu.com"}]
List的型是List.class,用反射从List.class获取构造函数构造的List,能存储Object,但Jackson并不懂得应该用User.class来构造元素,List.class包含的型信息是不足够的。
但List<User>的型是什么?List<User>.class这种语法并不存在,怎样才能获取List<User>的型?

装载了具体类型的元素的泛型容器的【型】是ParameterizedType类型,ParameterizedType是一个接口
(官方将ParameterizedType翻译成参数化类型)

	@SuppressWarnings("unused")
	private List<User> forGetType;

	@Test
	public void testParameterizedType() {
		try {
			Field userListField = getClass().getDeclaredField("forGetType");
			assertTrue(userListField.getGenericType() instanceof ParameterizedType);
			ParameterizedType userListType = (ParameterizedType)userListField.getGenericType();
			System.out.println(userListType.getClass());
			System.out.println(userListType.getRawType());
			Type[] typeArguments = userListType.getActualTypeArguments();
			for (Type type : typeArguments) {
				assertEquals(type, User.class);
				System.out.println(type);
			}
		} catch (SecurityException e) {
			e.printStackTrace();
			fail();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
			fail();
		}
	}

输出结果
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
interface java.util.List
class info.kuyur.jsondemo.models.User

forGetType只是为了获取List<User>的型而存在,不需要实例化。
第八行表明,List<User>的【型】确确实实是ParameterizedType
第十行透露了,Java内置的ParameterizedType实现是sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl

ParameterizedType的Type getRawType()接口,返回泛型容器自身的【型】。
ParameterizedType的Type[] getActualTypeArguments()接口,返回参与构造容器的型别参数们的【型】。
List<User>实际上是以User.class为型别参数,结合母容器List.class,构造出一种新的具体型:List<User>型,getActualTypeArguments()返回[User.class]。
同理,Map<Integer, User>实际上是以[Integer.class, User.class]为型别参数,结合母容器Map.class,构造出具体型:Map<Integer, User>型,getActualTypeArguments()返回[Integer.class, User.class]。
ParameterizedType可以反复递归,也即复杂似List<Map<Integer, User>>型同样能够用ParameterizedType表达出来。
ParameterizedType含有足够的【型】信息,让Jackson能够构造泛型容器中的元素。

(在这里说句题外话,学习Haskell对泛型的深入理解大有好处)

反序列化为List<User>,终于成功

	@Test
	public void testReadJsonFromString6() throws Exception {
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			Field userListField = UtilTest.class.getDeclaredField("forGetType");
			assertTrue(userListField.getGenericType() instanceof ParameterizedType);
			ParameterizedType userListType = (ParameterizedType)userListField.getGenericType();
			List<User> users  = Util.readJsonFromStream(bais, userListType);
			for (User user : users) {
				System.out.println(user);
				if (user.getName().equals("铅球万袋")) {
					assertEquals(user.getMailAddress(), "qian.qiu@wandai.com");
				}
				if (user.getName().equals("一桶浆糊")) {
					assertEquals(user.getMailAddress(), "yi.tong@jianghu.com");
				}
			}
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

为了获得List<User>型信息,定义了一个从来不会使用fotGetType,代码太丑陋,还有别的方法吗?

反射为我们提供了从函数的返回类型获取参数化类型信息的另外一个方法

public class Util {
	...
	public static ParameterizedType getParameterizedReturnType(Class<?> clazz, String methodName, Class<?>... param) {
		try {
			Method method = clazz.getDeclaredMethod(methodName, param);
			Type type = method.getGenericReturnType();
			if (type instanceof ParameterizedType) {
				return (ParameterizedType) type;
			} else {
				throw new RuntimeException(type.getClass() + "is not ParameterizedType");
			}
		} catch (SecurityException e) {
			throw new RuntimeException(e);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}
}

假如有那么一个客户端需要从json反序列化,可以通过方法自身的返回值去获取参数化类型信息

package info.kuyur.jsondemo.client;

import info.kuyur.jsondemo.models.User;
import info.kuyur.jsondemo.utils.Util;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.List;

public class UserClient {

	public List<User> getUserList() {
		Type type = Util.getParameterizedReturnType(getClass(), "getUserList");
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			return Util.readJsonFromStream(bais, type);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}
}

测试一下,转换无压力:

	@Test
	public void testGetUserList() {
		UserClient userClient = new UserClient();
		List<User> users = userClient.getUserList();
		for (User user : users) {
			System.out.println(user);
		}
	}

但动态从方法获取【型】还是太丑了,而且还必须有个方法才能用,这和先定义个”forGetType”没什么区别啊

于是再玩一下,实现自己的ParameterizedType
实际上sun已经给我们实现了一个,就在jaxb包中,将它拷贝出来,改改可见性

package info.kuyur.jsondemo.utils;

import java.lang.reflect.MalformedParameterizedTypeException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;

/**
 * {@link ParameterizedType} implementation.
 */
public class MoeParameterizedTypeImpl implements ParameterizedType {
	private Type[] actualTypeArguments;
	private Class<?> rawType;
	private Type ownerType;

	public MoeParameterizedTypeImpl(Class<?> rawType, Type[] actualTypeArguments, Type ownerType) {
		this.actualTypeArguments = actualTypeArguments;
		this.rawType = rawType;
		if (ownerType != null) {
			this.ownerType = ownerType;
		} else {
			this.ownerType = rawType.getDeclaringClass();
		}
		validateConstructorArguments();
	}

	private void validateConstructorArguments() {
		TypeVariable<?>[] formals = rawType.getTypeParameters();
		// check correct arity of actual type args
		if (formals.length != actualTypeArguments.length) {
			throw new MalformedParameterizedTypeException();
		}
		for (int i = 0; i < actualTypeArguments.length; i++) {
			// check actuals against formals' bounds
		}
	}

	public Type[] getActualTypeArguments() {
		return actualTypeArguments.clone();
	}

	public Class<?> getRawType() {
		return rawType;
	}

	public Type getOwnerType() {
		return ownerType;
	}

	/*
	 * From the JavaDoc for java.lang.reflect.ParameterizedType
	 * "Instances of classes that implement this interface must
	 * implement an equals() method that equates any two instances
	 * that share the same generic type declaration and have equal
	 * type parameters."
	 */
	@Override
	public boolean equals(Object o) {
		if (o instanceof ParameterizedType) {
			// Check that information is equivalent
			ParameterizedType that = (ParameterizedType) o;

			if (this == that)
				return true;

			Type thatOwner = that.getOwnerType();
			Type thatRawType = that.getRawType();

			return (ownerType == null ? thatOwner == null :	ownerType.equals(thatOwner)) &&
				(rawType == null ? thatRawType == null : rawType.equals(thatRawType)) &&
				Arrays.equals(actualTypeArguments, that.getActualTypeArguments());
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		return Arrays.hashCode(actualTypeArguments) ^
				(ownerType == null ? 0 : ownerType.hashCode()) ^
				(rawType == null ? 0 : rawType.hashCode());
	}

	@SuppressWarnings("rawtypes")
	public String toString() {
		StringBuilder sb = new StringBuilder();

		if (ownerType != null) {
			if (ownerType instanceof Class)
				sb.append(((Class) ownerType).getName());
			else
				sb.append(ownerType.toString());

			sb.append(".");

			if (ownerType instanceof MoeParameterizedTypeImpl) {
				// Find simple name of nested type by removing the
				// shared prefix with owner.
				sb.append(rawType.getName().replace(((MoeParameterizedTypeImpl) ownerType).rawType.getName() + "$",""));
			} else
				sb.append(rawType.getName());
		} else
			sb.append(rawType.getName());

		if (actualTypeArguments != null && actualTypeArguments.length > 0) {
			sb.append("<");
			boolean first = true;
			for (Type t : actualTypeArguments) {
				if (!first)
					sb.append(", ");
				if (t instanceof Class)
					sb.append(((Class) t).getName());
				else
					sb.append(t.toString());
				first = false;
			}
			sb.append(">");
		}

		return sb.toString();
	}
}

下面构造自己的萌萌的参数化类型(ownerType实际上可以完全无视)
List<User>型的构造:

Type[] typeArguments = new MoeParameterizedTypeImpl[1];
typeArguments[0] = new MoeParameterizedTypeImpl(User.class, new MoeParameterizedTypeImpl[0], null);
MoeParameterizedTypeImpl userListType = new MoeParameterizedTypeImpl(List.class, typeArguments, null);

User不是泛型,但用ParameterizedType仍然可以表达,第二行的等号右边即是User的ParameterizedType形式的型,可以认为User是一种退化的泛型

用萌萌的参数化类型反序列化,无压力

	@Test
	public void testReadJsonFromString7() throws UnsupportedEncodingException {
		Type[] typeArguments = new MoeParameterizedTypeImpl[1];
		typeArguments[0] = new MoeParameterizedTypeImpl(User.class, new MoeParameterizedTypeImpl[0], null);
		System.out.println(typeArguments[0]);
		MoeParameterizedTypeImpl userListType = new MoeParameterizedTypeImpl(List.class, typeArguments, null);
		System.out.println(userListType);
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			List<User> users  = Util.readJsonFromStream(bais, userListType);
			for (User user : users) {
				System.out.println(user);
				if (user.getName().equals("铅球万袋")) {
					assertEquals(user.getMailAddress(), "qian.qiu@wandai.com");
				}
				if (user.getName().equals("一桶浆糊")) {
					assertEquals(user.getMailAddress(), "yi.tong@jianghu.com");
				}
			}
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

Map<Integer, User>型,同样毫无压力:

	@Test
	public void testReadJsonFromString10() throws UnsupportedEncodingException {
		Type[] typeArguments = new MoeParameterizedTypeImpl[2];
		typeArguments[0] = new MoeParameterizedTypeImpl(Integer.class, new MoeParameterizedTypeImpl[0], null);
		typeArguments[1] = new MoeParameterizedTypeImpl(User.class, new MoeParameterizedTypeImpl[0], null);
		MoeParameterizedTypeImpl userMapType = new MoeParameterizedTypeImpl(Map.class, typeArguments, null);
		System.out.println(userMapType);
		String jsonString = "{\"1\" : {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"2\" : {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}}";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			Map<Integer, User> users = Util.readJsonFromStream(bais, userMapType);
			assertEquals(users.size(), 2);
			User user1 = users.get(Integer.valueOf(1));
			System.out.println(user1);
			User user2 = users.get(Integer.valueOf(2));
			System.out.println(user2);
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

如果拿ObjectMapper的readValue(String, Class<T>)来转换,因为型信息不足,转换结果将会变得面目全非

	@SuppressWarnings("unchecked")
	@Test
	public void testReadJsonFromString8() {
		String jsonString = "{\"1\" : {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"2\" : {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}}";
		Map<Integer, User> users = Util.readJsonFromString(jsonString, Map.class);
		assertEquals(users.size(), 2);

		User user1 = users.get(Integer.valueOf(1));
		System.out.println(user1); // null
		User user2 = users.get(Integer.valueOf(2));
		System.out.println(user2); // null

		Iterator<Integer> iter = users.keySet().iterator();
		while(iter.hasNext()) {
			Object val = users.get(iter.next());
			System.out.println(val);
			assertFalse(val instanceof User);
			assertTrue(val instanceof Map);
		}

		Object userMap1 = users.get("1"); // actually key is String type
		assertNotNull(userMap1);
		System.out.println(userMap1); // actually value is LinkedHashMap type

		Object userMap2 = users.get("2");
		assertNotNull(userMap2);
		System.out.println(userMap2);
	}

再来玩一下自定义的泛型类型

定义一个三元组:

package info.kuyur.jsondemo.models;

public class Triple<F, S, T> {

	private F first;
	private S second;
	private T third;

	public Triple() {
	}

	public Triple(F first, S second, T third) {
		this.first = first;
		this.second = second;
		this.third = third;
	}

	public F getFirst() {
		return first;
	}

	public void setFirst(F first) {
		this.first = first;
	}

	public S getSecond() {
		return second;
	}

	public void setSecond(S second) {
		this.second = second;
	}

	public T getThird() {
		return third;
	}

	public void setThird(T third) {
		this.third = third;
	}

	@Override
	public String toString() {
		return "Triple [first=" + first + ", second=" + second + ", third="
				+ third + "]";
	}
}

从下面的json反序列化出Triple<User, Archive,Comment>,注意json中的字段顺序不应该对转换有影响
{"second":{"ID":1000,"标题":"18岁的那天","正文":"18岁那天的早上...","作者":{"mailAddress":"yi.tong@jianghu.com","name":"一桶浆糊"}},"third":{"作者":{"mailAddress":"qian.qiu@wandai.com","name":"铅球万袋"},"回复":"骚年よ、大志を抱け"},"first":{"mailAddress":"yi.tong@jianghu.com","name":"一桶浆糊"}}

	@Test
	public void testReadJsonFromString16() throws UnsupportedEncodingException {
		Type[] typeArguments = new MoeParameterizedTypeImpl[3];
		typeArguments[0] = new MoeParameterizedTypeImpl(User.class, new MoeParameterizedTypeImpl[0], null);
		typeArguments[1] = new MoeParameterizedTypeImpl(Archive.class, new MoeParameterizedTypeImpl[0], null);
		typeArguments[2] = new MoeParameterizedTypeImpl(Comment.class, new MoeParameterizedTypeImpl[0], null);
		MoeParameterizedTypeImpl tripleType = new MoeParameterizedTypeImpl(Triple.class, typeArguments, null);
		System.out.println(tripleType);
		String jsonString = "{\"second\":{\"ID\":1000,\"标题\":\"18岁的那天\",\"正文\":\"18岁那天的早上...\",\"作者\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}},\"third\":{\"作者\":{\"mailAddress\":\"qian.qiu@wandai.com\",\"name\":\"铅球万袋\"},\"回复\":\"骚年よ、大志を抱け\"},\"first\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}}";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			Triple<User, Archive, Comment> triple = Util.readJsonFromStream(bais, tripleType);
			System.out.println(triple.getFirst());
			System.out.println(triple.getSecond());
			System.out.println(triple.getThird());
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

输出:

info.kuyur.jsondemo.models.Triple<info.kuyur.jsondemo.models.User, info.kuyur.jsondemo.models.Archive, info.kuyur.jsondemo.models.Comment>
User [name=一桶浆糊, mailAddress=yi.tong@jianghu.com]
Archive [blogId=1000, title=18岁的那天, text=18岁那天的早上..., author=User [name=一桶浆糊, mailAddress=yi.tong@jianghu.com]]
Comment [content=骚年よ、大志を抱け, author=User [name=铅球万袋, mailAddress=qian.qiu@wandai.com]]

完美转换!

User/Archive/Comment是退化的参数化类型,因此Triple<User, Archive, Comment>的参数化类型的构造过程可以简化为:

Type[] typeArguments = {User.class, Archive.class, Comment.class};
MoeParameterizedTypeImpl tripleType = new MoeParameterizedTypeImpl(Triple.class, typeArguments, null);

下篇介绍最后的杀器,Jackson提供的TypeReference。

Java泛型和JSON的反序列化(上)

开发RESTful应用的客户端时,不可避免要实现JSON或xml的反序列化和序列化。对于HTML客户端,JSON和JavaScript对象之间的转换是自然而然理所当然的事,JavaScript框架不实现就算不上框架。但对于Java语言的客户端,选择却不是那么多。Jackson JSON应该是最强大的JSON和Java对象转换工具库。

Jackson官方有个五分钟入门教程,用法非常的简洁。

下文的型都是指java.lang.reflect.Type,
java1.5引入了泛型,Type正是对一切数据类型的抽象,万物皆有型,
内置数据类型有型,如int.class,boolean.class,
对象类型有型,如Integer.class,和Integer i = 1; i.getClass();等价

Type type1 = Integer.class;
Integer i = 1;
Type type2 = i.getClass();
assertEquals(type1, type2);

同时【型】还是一种Java对象。Class是Type的实现,是【对象】(Object)这一类数据类型的型。

将反序列化功能写成一个通用的工具类:(使用ObjectMapper的接口readValue(String, Class<T>))

package info.kuyur.jsondemo.utils;

import java.io.IOException;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

public class Util {
	private Util() {}

	/**
	 * Only JacksonAnnotation supported.
	 * @param jsonString JSON string
	 * @param type Class type. Generic type is not supported.
	 * @return
	 */
	public static <T> T readJsonFromString(String jsonString, Class<T> type) {
		ObjectMapper mapper = new ObjectMapper();
		try {
			return mapper.readValue(jsonString, type);
		} catch (JsonParseException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		} catch (JsonMappingException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		} catch (IOException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		}
	}

反序列化函数使用了泛型(本来Jackson的ObjectMapper就使用了泛型)

定义几个数据模型,然后再测试一下这个反序列化函数的转换能力

User.java,不加注任何标签

package info.kuyur.jsondemo.models;

public class User {

	private String name;
	private String mailAddress;

	public User() {
		super();
	}

	public User(String name, String mailAddress) {
		super();
		this.name = name;
		this.mailAddress = mailAddress;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getMailAddress() {
		return mailAddress;
	}

	public void setMailAddress(String mailAddress) {
		this.mailAddress = mailAddress;
	}

	@Override
	public String toString() {
		return "User [name=" + name + ", mailAddress=" + mailAddress + "]";
	}
}

Archive.java,加注JAXBAnnotation类型的标签

package info.kuyur.jsondemo.models;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Archive {

	@XmlElement(name="ID")
	private int blogId;

	@XmlElement(name="标题")
	private String title;

	@XmlElement(name="正文")
	private String text;

	@XmlElement(name="作者")
	private User author;

	public Archive() {
		super();
	}

	public Archive(int blogId, String title, String text, User author) {
		super();
		this.blogId = blogId;
		this.title = title;
		this.text = text;
		this.author = author;
	}

	public int getBlogId() {
		return blogId;
	}

	public void setBlogId(int blogId) {
		this.blogId = blogId;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	public User getAuthor() {
		return author;
	}

	public void setAuthor(User author) {
		this.author = author;
	}

	@Override
	public String toString() {
		return "Archive [blogId=" + blogId + ", title=" + title + ", text="
				+ text + ", author=" + author + "]";
	}
}

Comment.java,加注JacksonAnnotation类型的标签

package info.kuyur.jsondemo.models;

import org.codehaus.jackson.annotate.JsonProperty;

public class Comment {

	@JsonProperty("作者")
	private User author;

	@JsonProperty("回复")
	private String content;

	public User getAuthor() {
		return author;
	}

	public void setAuthor(User author) {
		this.author = author;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	@Override
	public String toString() {
		return "Comment [content=" + content + ", author=" + author + "]";
	}
}

User的一条json字符串,成员变量名作为key,反序列化通过。
{"name": "一桶浆糊", "mailAddress": "yi.tong@jianghu.com"}

	@Test
	public void testReadJsonFromString1() {
		String jsonString = "{\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}";
		User user = Util.readJsonFromString(jsonString, User.class);
		System.out.println(user);
		assertEquals(user.getName(), "一桶浆糊");
		assertEquals(user.getMailAddress(), "yi.tong@jianghu.com");
	}

Jackson能从User的型(Class<User>)中,以json字符串中的key(name和mailAddress)来搜索成员变量名,再通过规范化的setter设置成员变量值。
含有参数的public构造子对Jackson是没意义的,因为对java而言参数变量名没有意义,有意义的只是参数类型。
以参数类型和函数名为检索条件通过反射可以确定唯一的函数,以参数类型为检索条件可以确定唯一的构造子,但Jackson不会去使用这种带参数的构造子

	public User(String name, String mailAddress) {
		super();
		this.name = name;
		this.mailAddress = mailAddress;
	}
	public User(String mingzi, String youxiang) {
		super();
		this.name = mingzi;
		this.mailAddress = youxiang;
	}

上面两个构造子完全一样,它们的函数原型相同,都是public User(String, String),你不能在一个类里同时定义两个原型相同的函数。
再次强调,参数名没有意义,如果调用这种构造子,Jackson不会懂得name和mailAddress那个先,那个后。
因此,为了让Jackson能自动反序列化,数据模型的public无参构造子以及规范化setter必须实现

Archive的一条json字符串,key在JAXBAnnotation类型的标注中定义
{"ID": "1000", "标题": "18岁的那天", "正文": "18岁那天的早上..."}

	@Test
	public void testReadJsonFromStringButFail() {
		String jsonString = "{\"ID\": \"1000\", \"标题\": \"18岁的那天\", \"正文\": \"18岁那天的早上...\"}";
		Archive archive = Util.readJsonFromString(jsonString, Archive.class);
		System.out.println(archive);
	}

很可惜,反序列化失败,ObjectMapper不支持JAXBAnnotation类型标签。

Comment的一条json字符串,key在JacksonAnnotation类型的标注中定义
{"作者": {"name": "铅球万袋", "mailAddress": "qian.qiu@wandai.com"}, "回复": "骚年よ、大志を抱け"}

	@Test
	public void testReadJsonFromString4() {
		String jsonString = "{\"作者\": {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"回复\": \"骚年よ、大志を抱け\"}";
		Comment comment = Util.readJsonFromString(jsonString, Comment.class);
		System.out.println(comment);
		assertEquals(comment.getContent(), "骚年よ、大志を抱け");
		assertEquals(comment.getAuthor().getName(), "铅球万袋");
		assertEquals(comment.getAuthor().getMailAddress(), "qian.qiu@wandai.com");
	}

反序列化通过。同时Comment的成员变量author也反序列化成功。因此Jackson反序列化复杂的数据模型没有问题。

再来测试泛型

数组类型的一条json
[{"name": "铅球万袋", "mailAddress": "qian.qiu@wandai.com"}, {"name": "一桶浆糊", "mailAddress": "yi.tong@jianghu.com"}]

	@Test
	public void testReadJsonFromString5() {
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		User[] users = Util.readJsonFromString(jsonString, User[].class);
		for (User user : users) {
			System.out.println(user);
		}
	}

转换成功,ObjectMapper支持转换到特定类型的数组。
User[] users的型也即User[].class是GenericArrayType,通过getGenericComponentType()接口,可以获得User的型,这样Jackson就可以成功构造User类型的数组。

但如果我们想转换成泛型容器呢?

	@Test
	public void testReadJsonFromString5ButFail() {
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		List<User> users = Util.readJsonFromString(jsonString, List.class);
		for (User user : users) {
			System.out.println(user);
		}
	}

这种转换不会成功,List.class提供给Jackson的信息并不足够,Jackson并不懂得List里面要装的是User类型的数据。

简单的字符串数组/整型数组/布尔数组是能转换成List<String>,List<Integer>,List<Boolean>的。
例如,下面的json反序列化通过

	@Test
	public void testReadJsonFromString12() {
		String jsonString = "[1, 2]";
		List<Integer> integers = Util.readJsonFromString(jsonString, List.class);
		for (Integer i : integers) {
			System.out.println(i);
		}
	}

Jackson能从json中推导出List中所要装的东西的类型。
(注意[1, 2]是整型数组,["1", "2"]是字符串数组,json自身能携带少量的型信息)

testReadJsonFromString5ButFail用例中,事实上Jackson将(希望是User类型的)json对象转换成了LinkedHashMap类型的对象,LinkedHashMap当然不能转换到User类型,于是就抛出了ClassCastException

将容器修改为List<Object>类型:

	@Test
	public void testReadJsonFromString13() {
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		List<Object> users = Util.readJsonFromString(jsonString, List.class);
		for (Object user : users) {
			System.out.println(user);
		}
	}

或者更直接的List<LinkedHashMap>类型:

	@Test
	public void testReadJsonFromString14() {
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		List<LinkedHashMap> users = Util.readJsonFromString(jsonString, List.class);
		for (LinkedHashMap user : users) {
			System.out.println(user);
		}
	}

可以反序列化成功,但对象已经面目全非,不是我们想要的了。

但我们很想很想要List<User>类型的数据啊,怎么办?那是下一篇的故事了。

总结一下:Jackson的ObjectMapper的使用有两个限制:
一是标签类型只能是JacksonAnnotation或者不加标签,数据模型不加标签的话,json就只能迁就数据模型,但如果json的字段名(key)不是我们所能控制的话,比如来一个"城市": "广州",麻烦可就大了;
二是对泛型容器的有限支持(这句话并不准确,事实上ObjectMapper的另外一个接口支持更加强大的【型】——ParameterizedType,可以实现我们的需求)。

8,295 次浏览 | 没有评论
2012年12月14日 | 归档于 技术, 程序