如何覆盖JSONEncoder对二进制数据的行为?

时间:2015-08-07 04:07:38

标签: python json python-2.7

我在Python 2.7.10中工作,我有一些二进制数据:

binary_data = b'\x01\x03\x00\x00 \xe6\x10\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

(如果您非常好奇,那就是几何体的Extended WKB。)

实际上,我在dict

中的某处有这些数据
my_data = {
    'something1': 5.5,
    'something2': u'Some info',
    'something3': b'\x01\x03\x00\x00 \xe6\x10\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
}

我想将其序列化为JSON来存储它。问题是我收到错误,因为json错误地尝试将其解释为UTF-8:

>>> json.dumps(my_data)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "C:\Python\27\Lib\json\__init__.py", line 243, in dumps
    return _default_encoder.encode(obj)
  File "C:\Python\27\Lib\json\encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Python\27\Lib\json\encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xe6 in position 5: invalid continuation byte

我可以手动编码:

my_serializable_data = dict(my_data.items())
my_serializable_data['something3'] = binascii.b2a_base64(my_serializable_data['something3'])
json.dumps(my_serializable_data)

给出了一个很好的

'{"something2": "Some info", "something3": "AQMAACDmEAAAAQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAPA/AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\\n", "something1": 5.5}'

但这会很麻烦,因为我需要在整个应用程序中重做这个。我更倾向于为此二进制文件自定义json的行为。通常情况下,您可以通过覆盖json来告知JSONEncoder.default如何序列化某些内容,如下所示:

class MyJsonEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, str):
            return binascii.b2a_base64(o)

        return super(MyJsonEncoder, self).default(o)

但是这没有效果,大概是因为str的处理被硬编码到JSONEncoder中:

>>> json.dumps(my_data, cls=MyJsonEncoder)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "C:\Python\27\Lib\json\__init__.py", line 250, in dumps
    sort_keys=sort_keys, **kw).encode(obj)
  File "C:\Python\27\Lib\json\encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Python\27\Lib\json\encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xe6 in position 5: invalid continuation byte

覆盖JSONEncoder.encode 工作,但我需要从内置库中重建大量逻辑,因为该方法知道如何挖掘任意级别和{的组合{1}}和list s。我不想那样做;它会很快变得难看并且容易出错。 (另外,看一下源代码,看起来这个逻辑可能在dict中的模块的全局方法中,这使得这个想法更加混乱。)

这里的一个重要注意事项是反序列化它以供以后使用对于这种情况不是问题。这是用于记录目的;当这些数据被反序列化时,它将由开发人员查看。如果他们真的需要对数据做些什么,他们可以手动解码它就好了。我也愿意做出这样的交易:如果某些文本作为json而不是str通过,则无论如何都会进行base64编码。 (或者,如果我的代码包含除可打印ASCII之外的任何字符,我可能会修改我的代码只对base64进行编码,但我甚至不能做出这个决定,直到我能解决我在这里问的问题。)

那么如何在不尝试重建unicode过多的情况下覆盖此行为?

1 个答案:

答案 0 :(得分:1)

真的需要重建一切本身。一个便宜的出路是按照你的建议去做并覆盖encode,但是用清理后的数据构建一个新的dict

但是,如果您希望灵活地处理二进制数据的任意输入而不必重新实现所有内容,您可以选择在json.encoder模块中修补几个函数。这样做的受控方式是使用特定的编码器来确保默认行为不受影响。

import json
import json.encoder
import binascii

_default_encode_basestring = json.encoder.encode_basestring
_default_encode_basestring_ascii = json.encoder.encode_basestring_ascii

def _check_string(s):
    if isinstance(s, str):
        try:
            s.decode('utf8')
        except UnicodeDecodeError:
            return False
    return True

def _encode_basestring(s):
    if not _check_string(s):
        s = binascii.b2a_base64(s)
    return _default_encode_basestring(s)

def _encode_basestring_ascii(s):
    if not _check_string(s):
        s = binascii.b2a_base64(s)
    return _default_encode_basestring_ascii(s)


class MyJsonEncoder(json.JSONEncoder):

    def encode(self, o):
        json.encoder.encode_basestring = _encode_basestring
        json.encoder.encode_basestring_ascii = _encode_basestring_ascii
        result = super(MyJsonEncoder, self).encode(o)
        json.encoder.encode_basestring = _default_encode_basestring
        json.encoder.encode_basestring_ascii = _default_encode_basestring_ascii     
        return result

一个免费的例子:

>>> my_data = {
...     'something1': 5.5,
...     'something2': u'Some info',
...     'something3': b'\x01\x03\x00\x00 ...\x00\x00',
... }
>>> import json
>>> r = json.dumps(my_data, cls=MyJsonEncoder)
>>> print r
{"something2": "Some info", "something3": "AQMAACDm...AAAA==\n", "something1": 5.5}
>>> r = json.dumps(my_data)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/json/__init__.py", line 243, in dumps
    return _default_encoder.encode(obj)
  File "/usr/lib/python2.7/json/encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python2.7/json/encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xe6 in position 5: invalid continuation byte

嵌套测试。

>>> json.dumps({'some': {'nested': {'data': [b'\xe0\x01\x02\x03?']}}}, cls=MyJsonEncoder)
'{"some": {"nested": {"data": ["4AECAz8=\\n"]}}}'