Python JSON模块解码中文的BUG

很多语言或协议选择使用 ASCII 字符 “\”(backslash,0x5c) 作为字符串的转义符,包括 JSON 中的字符串。一般来说,使用 Python 中的 JSON 模块编码英文,不会存在转义符的问题。但如果使用 JSON 模块编解码中文,就可能面临着中文字符包含转义符带来的 bug。本篇文章给出了一个 badcase。

中文解码错误

测试用例文件里面包含繁体的“運動”二字,使用 GB18030 编码。使用 json 解码的错误如下:

$ cat decode.dat
{"a":"運動"}
$ python
>>> import json
>>> fp=open('decode.dat', 'r')
>>> json.load(fp, encoding='gb18030')
Traceback (most recent call last):
  File "", line 1, in 
  File "/home/yangwb/local/lib/python2.7/json/__init__.py", line 278, in load
    **kw)
  File "/home/yangwb/local/lib/python2.7/json/__init__.py", line 339, in loads
    return cls(encoding=encoding, **kw).decode(s)
  File "/home/yangwb/local/lib/python2.7/json/decoder.py", line 360, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/home/yangwb/local/lib/python2.7/json/decoder.py", line 376, in raw_decode
    obj, end = self.scan_once(s, idx)
UnicodeDecodeError: 'gb18030' codec can't decode byte 0xdf in position 0: incomplete
multibyte sequence

发生这个问题的原因,就存在于“運”字的编码之中。“運”的 GB18030 编码是 0xdf5c,由于第二个字符与转义符 “\” 编码相同,所以剩下的这个 0xdf 就被认为是一个 incomplete multibyte sequence。

我本来认为,既然已经提供了编码,json 模块就能够区分汉字与转义符(所以我觉得这应该是 json 的一个 bug)。但从实验来看,并非如此。对于一些不需提供字符编码的 JSON 解码器来说,我们倒可以用一种比较 tricky 的方法绕过上面这个问题,即在“運”字后面加一个额外的转义符:

{"a":"運\動"}

遗憾的是,这种方法对 Python 的 json 模块不适用。我仍不知道该如何解决这个解码问题。

中文编码——没错误!

对于相同的 case,Python 倒是能够编码成功:

$ cat in.dat
運動
$ python
>>> import json
>>> in_str = open('in.dat', 'r').read()
>>> out_f = open('out.dat', 'w', 0)
>>> dump_str = json.dumps({'a': in_str}, ensure_ascii=False, encoding='gb18030')
>>> out_f.write(dump_str.encode('gb18030'))
$ cat out.dat
{"a": "運動"}

所以这件事情就把我给搞糊涂了,Python 的 json 模块不能解码自己编码的 json 串。所以我觉得这可能是一个 bug,或者至少是 2.7.1 版本的 bug。

PS: 要仔细看文档

20120516:经网友 TreapDB 提醒,加载字符串时自己做 Unicode 转换,貌似能够解决这个问题。

$ cat decode.dat
{"a":"運動"}
$ python
>>> import json
>>> in_str = open('decode.dat', 'r').read().decode('gb18030')
>>> json.loads(in_str)

回头仔细看了一下 json 的文档,其中有这么一段:

Encodings that are not ASCII based (such as UCS-2) are not allowed, and should be wrapped with codecs.getreader(encoding)(fp), or simply decoded to a unicode object and passed to loads().

已经注明了 encoding 不支持非 ASCII-based 编码的参数,所以应该使用 getreader 进行转码,而不是让 json 模块去转码。看来是我没读懂文档,大惊小怪了,回家面壁去!

>>> json.load(codecs.getreader('gb18030')(fp))

请测试 JabRef 2.4.3 中文版

JabRef 2.4.3(未发布)对中文用户的一大改进就是开始支持中文界面。中文化的工作本来是由另外一个朋友和我共同来做,但由于他工作繁忙,后来就是我一个人在进行了。

目前我已经翻译了大约 80% 的样子,菜单和选项窗口基本上都翻译完了,还剩下一些少用的选项和状态栏信息。因为水平有限,也并不能遍用 JabRef 的所有 feature,很多地方可能翻译的不合适,所以想找大家帮忙测试一下,给提些意见。(JabRef 使用问题请参考“文献管理软件 JabRef 快速入门”)

您可以在 http://share.solrex.org/ibuild/ 下载到编译好的 jar 文件,jar 文件以 JabRef-2.4.3-Chinese-编译日期.jar 格式命名。您可以使用

java -jar JabRef-2.4.3-Chinese-编译日期.jar

来执行它。

如果您已经安装 JabRef 2.4.2 版,您可以覆盖 JabRef 2.4.2 的 jar 来实现直接从原有快捷方式启动 JabRef 2.4.3,方法为:

Windows:

首先到您的 JabRef 2.4.2 安装目录,我们假设为:C:Program FilesJabRef,备份 JabRef-2.4.2.jar(修改文件名为另外的名字即可),然后将 JabRef-2.4.3-Chinese-编译日期.jar 拷贝到安装目录 C:Program FilesJabRef 下,修改文件名为 JabRef-2.4.2.jar,然后再执行 JabRef.exe 就调用的是 2.4.3 版本的 JabRef 了。

Linux:

sudo mv /usr/share/java/JabRef-2.4.2.jar /usr/share/java/JabRef-2.4.2.jar.bak
sudo cp JabRef-2.4.3-Chinese-编译日期.jar /usr/share/java/JabRef-2.4.2.jar
当然,您也可以修改软链接 /usr/share/java/jabref.jar 指向 JabRef-2.4.3-Chinese-编译日期.jar

启动 JabRef 2.4.3 之后,请到 Options->Preferences->General->Language 中选择 Chinese,然后重启 JabRef 才能进入中文界面。目前只有简体中文,但是我已经提请维护者增加并区分简体中文和繁体中文,大概会在以后添加。

您可以通过以下途径将有关翻译的意见反馈给我:
1. 评论这篇文章,任何人。
2. 在我侧栏的 Google Friend Connect 留言簿留言,需要 Google 或 OpenID 帐户。
3. 给我发送 Email,我的联系方式可以在导航栏的“关于我”链接中找到。

目前所有翻译都是我的工作,所以所有错误都该归我一个人身上。有您的帮助,我才能不将这个错误带给更多人,我将很感激您为此付出的时间和努力。

下面是中文版的一个截图:
JabRef 中文版界面

Therapy

上次说到洗牙,多位朋友在 Blog 和 Space 里表示关心,非常感谢,所以这里来点儿后续报导,免得大家挂念。

大家都说洗牙好,这一点在我身上充分得到了证实。将近三年的牙龈出血,简单的洗了次牙,真的好了。从洗完牙的第二天开始,刷牙和吃苹果的时候就再也没见流血了,效果可谓是立竿见影。所以,我非常推荐有长期牙龈出血病史同志尝试去洗下牙!

一个病遇到良医或者庸医,结果可能完全不一样。我还有个例子,大概从到北京实习开始,由于饮食和休息的不规律,嘴里经常有口腔溃疡,一般还比较厉害。刚开始用贴片,但是老没有效果,后来使用西瓜霜粉剂,虽然效果也不是很明显,但是粉剂洒上去之后,起码没那么疼了。今年春节,于洁妹子给我抓了六剂中药,回家让我妈熬了,每天一剂。这还是我第一次吃汤药,真苦啊!那句俗话:“良药苦口利于病!”又应到我身上了。从春节到现在三个月了吧,虽然还是出现了两次口腔溃疡,但都是在萌芽期就消失了,和我以前一疼四五天那种溃疡完全不可同日而语。

我以前对中药充满了不信任,现在看来,一些西药很难根治的慢性病,尝试一下中药疗法也无妨。我现在最大的愿望是:谁能(用亲身经历)告诉我鼻炎有什么有效的疗法啊?

=================== 我是跑题的分割线 ====================

昨天看到有同学在博客里贴出来余光中的《听听那冷雨》,忽然发现,自己好久没有读过这样美丽的中文了。这匆匆的生活啊!

于是下午跑去第三极书局,买了两本《左手的掌纹》和《荷塘月色》回来。一进书店,我就忍不住想买书,看惯了影印版计算机书那动辄六七十的售价,文学书显得好便宜啊!虽然挑了好几本书,理科生的思维还是占了上风,把名单记下来,只拿最便宜的两本去付帐。肯定有人奇怪,为什么?因为我觉得越贵的书在网店打大折扣的可能性越大,所以当然要在网店买喽!这就是一个穷学生的购书理念,不买盗版书,但一定要捡最便宜的地方买。

回来上网一查,果然不出我所料,《左手的掌纹》和《荷塘月色》在网店很少能打到八折以下的,但是我看中的另外两本书,在卓越-亚马逊的价格都是六折以下,比第三极要便宜许多,又免费送货,立马订购了。

在第三极,我也向服务员询问了一下柏杨先生的《丑陋的中国人》,答曰近日此书十分火爆,问询者比比皆是,但书店备书无多,业已脱销,正加紧配货。而我常去的两家网店也是暂无存货,只好等有机会再读了。柏杨先生生于河南开封,要说起来,我们还算是老乡呢。

走到中关村广场的时候,特地留意了一下,家乐福门口附近站了六七个警察和工作人员,很慵懒地走来走去,看来局势还是很平稳的。