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

jpm*_*c26 5 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'
Run Code Online (Sandbox Code Playgroud)

(如果您真的好奇,那是几何体的扩展 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',
}
Run Code Online (Sandbox Code Playgroud)

我想将其序列化为 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
Run Code Online (Sandbox Code Playgroud)

我可以手动编码:

>>> 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
Run Code Online (Sandbox Code Playgroud)

给了一个很好的

'{"something2": "Some info", "something3": "AQMAACDmEAAAAQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAPA/AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\\n", "something1": 5.5}'
Run Code Online (Sandbox Code Playgroud)

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

my_serializable_data = dict(my_data.items())
my_serializable_data['something3'] = binascii.b2a_base64(my_serializable_data['something3'])
json.dumps(my_serializable_data)
Run Code Online (Sandbox Code Playgroud)

但这没有效果,大概是因为 的处理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
Run Code Online (Sandbox Code Playgroud)

覆盖JSONEncoder.encode 应该可行,但我需要从内置库中重建大量逻辑,因为该方法知道如何深入挖掘lists 和dicts 的任意级别和组合。我宁愿不这样做;它会很快变得丑陋并且容易出错。(此外,查看源代码,看起来该逻辑可能位于中模块的全局json方法中,这使得这个想法更加混乱。)

这里的一个重要注意事项是,反序列化它以供以后使用对于这种情况不是问题。这是为了记录目的;当这些数据被反序列化时,它将供开发人员查看。如果他们确实需要对数据做一些事情,他们可以手动解码它。我也愿意做出权衡,如果某些文本作为 astr而不是 a出现unicode,无论如何它都会被进行 Base64 编码。(或者,如果我的代码包含可打印 ASCII 之外的任何字符,我可能会将其修改为仅对其进行 base64 编码,但在我能够解决我在这里询问的问题之前,我什至无法做出该决定。)

那么我怎样才能覆盖这种行为而不尝试重建太多呢JSONEncoding

met*_*ter 2

您实际上并不需要重建一切本身。一种廉价的方法是按照您的建议进行操作并覆盖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
Run Code Online (Sandbox Code Playgroud)

运行一个免费的示例:

>>> 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
Run Code Online (Sandbox Code Playgroud)

嵌套测试。

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