服务器响应中的BOM导致JSON解析错误

8 投票
3 回答
4808 浏览
提问于 2025-04-17 13:42

我正在尝试写一个Python脚本,目的是向一个网络服务器发送一些JSON数据,并获取一些JSON数据作为回应。我在StackOverflow上拼凑了几个不同的例子,感觉现在的代码大部分是能工作的。

import urllib2
import json

url = "http://foo.com/API.svc/SomeMethod"
payload = json.dumps( {'inputs': ['red', 'blue', 'green']} )
headers = {"Content-type": "application/json;"}

req = urllib2.Request(url, payload, headers)
f = urllib2.urlopen(req)
response = f.read()
f.close()

data = json.loads(response) # <-- Crashes

但是最后一行出现了一个错误:

ValueError: 无法解码JSON对象

当我查看response时,发现里面有有效的JSON数据,但开头的几个字符是一个BOM(字节顺序标记):

>>> response
'\xef\xbb\xbf[\r\n  {\r\n    ... Valid JSON here

所以,如果我手动去掉前面三个字节:

data = json.loads(response[3::])

一切就正常了,response也成功转换成了一个字典。

我的问题:

感觉有点奇怪的是,当你给json一个BOM时,它就会出错。有没有什么方法可以让urllib或者json库知道这是一个UTF8字符串,并且正确处理它?我不想手动去掉前面的三个字节。

3 个回答

0

因为我的声望不够,不能评论,所以我就写个回答。

我通常在需要保持一个StreamWriter的底层Stream打开时遇到这个问题。不过,那个可以让底层Stream保持打开的选项,需要指定一种编码(大多数情况下是UTF8),下面是如何做到输出BOM的方法。

/* Since Encoding.UTF8 (the one you'd normally use in those cases) **emits**
 * the BOM, use whats below instead!
 */

// UTF8Encoding has an overload which enables / disables BOMs in the output
UTF8Encoding encoding = new UTF8Encoding(false);

using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms, encoding, 4096, true))
using (JsonTextWriter jtw = new JsonTextWriter(sw))
{
    serializer.Serialize(jtw, myObject);
}
5

如果我不是唯一一个遇到这个问题的人,而是使用 requests 模块而不是 urllib2,那么这里有一个在 Python 2.6 和 3.3 中都能用的解决办法:

import requests
r = requests.get(url, params=my_dict, auth=(user, pass))
print(r.headers['content-type'])  # 'application/json; charset=utf8'
if r.text[0] == u'\ufeff':  # bytes \xef\xbb\xbf in utf-8 encoding
    r.encoding = 'utf-8-sig'
print(r.json())
12

你可能应该去找一下这个服务的负责人,跟他们说一下,因为在UTF-8文本中出现字节顺序标记(BOM)是没有意义的。字节顺序标记是用来区分字节顺序的,而UTF-8本身就是定义为小端格式的。

不过,理想情况下,你应该在对字节进行任何操作之前先解码它们。幸运的是,Python有一个可以识别并去掉字节顺序标记的编码器:utf-8-sig

>>> '\xef\xbb\xbffoo'.decode('utf-8-sig')
u'foo'

所以你只需要:

data = json.loads(response.decode('utf-8-sig'))

撰写回答