如何在Python中使json.dumps忽略不可序列化的字段

mz8*_*z8i 7 python json construct python-3.x

我试图序列化使用Construct2.9库解析一些二进制数据的输出.我想将结果序列化为JSON.

packet是Construct类的实例Container.

显然它包含一个隐藏_io的类型BytesIO- 请参阅dict(packet)下面的输出:

{
'packet_length': 76, 'uart_sent_time': 1, 'frame_number': 42958, 
'subframe_number': 0, 'checksum': 33157, '_io': <_io.BytesIO object at 0x7f81c3153728>, 
'platform':661058, 'sync': 506660481457717506, 'frame_margin': 20642,
'num_tlvs': 1, 'track_process_time': 593, 'chirp_margin': 78,
'timestamp': 2586231182, 'version': 16908293
}
Run Code Online (Sandbox Code Playgroud)

现在,调用json.dumps(packet)显然会导致TypeError:

...

File "/usr/lib/python3.5/json/__init__.py", line 237, in dumps
    **kw).encode(obj)
File "/usr/lib/python3.5/json/encoder.py", line 198, in encode
    chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python3.5/json/encoder.py", line 256, in iterencode
    return _iterencode(o, 0)
File "/usr/lib/python3.5/json/encoder.py", line 179, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <_io.BytesIO object at 0x7f81c3153728> is not JSON serializable
Run Code Online (Sandbox Code Playgroud)

然而,我感到困惑的是,运行json.dumps(packet, skipkeys=True)导致完全相同的错误,而我希望它跳过该_io字段.这里有什么问题?为什么skipkeys不允许我跳过这个_io领域?

我通过覆盖JSONEncoder和返回类型None字段来获取代码BytesIO,但这意味着我的序列化字符串包含大量"_io": null元素,我根本不想拥有它们...

Mar*_*ers 13

带有前导_下划线的键并不是真正的"隐藏",它们只是JSON的更多字符串.Construct Container类只是一个带有排序的字典,_io键不是该类的特殊内容.

您有两种选择:

  • 实现一个default只返回替换值的钩子.
  • 过滤掉序列化之前无法使用的键值对.

也许是第三个,但是对Construct项目页面的随意扫描并没有告诉我它是否可用:具有Construct输出JSON或至少一个与JSON兼容的字典,可能是通过使用适配器.

默认挂钩无法阻止将_io键添加到输出中,但至少可以避免错误:

json.dumps(packet, default=lambda o: '<not serializable>')
Run Code Online (Sandbox Code Playgroud)

过滤可以递归完成; 该@functools.singledispatch()装饰可以帮助保持这样的代码清洁:

from functools import singledispatch

_cant_serialize = object()

@singledispatch
def json_serializable(object, skip_underscore=False):
    """Filter a Python object to only include serializable object types

    In dictionaries, keys are converted to strings; if skip_underscore is true
    then keys starting with an underscore ("_") are skipped.

    """
    # default handler, called for anything without a specific
    # type registration.
    return _cant_serialize

@json_serializable.register(dict)
def _handle_dict(d, skip_underscore=False):
    converted = ((str(k), json_serializable(v, skip_underscore))
                 for k, v in d.items())
    if skip_underscore:
        converted = ((k, v) for k, v in converted if k[:1] != '_')
    return {k: v for k, v in converted if v is not _cant_serialize}

@json_serializable.register(list)
@json_serializable.register(tuple)
def _handle_sequence(seq, skip_underscore=False):
    converted = (json_serializable(v, skip_underscore) for v in seq)
    return [v for v in converted if v is not _cant_serialize]

@json_serializable.register(int)
@json_serializable.register(float)
@json_serializable.register(str)
@json_serializable.register(bool)  # redudant, supported as int subclass
@json_serializable.register(type(None))
def _handle_default_scalar_types(value, skip_underscore=False):
    return value
Run Code Online (Sandbox Code Playgroud)

我上面的实现也是一个额外的skip_underscore参数,以显式跳过_开头有字符的键.这将有助于跳过Construct库正在使用的所有其他"隐藏"属性.

由于Containerdict子类,上面的代码将自动处理诸如的实例packet.


Dav*_*ruz 10

正如之前所有答案中正确指出的那样,忽略不可序列化的字段需要大量的额外逻辑。

如果您真的不需要排除该字段,则可以改为生成默认值:

def safe_serialize(obj):
  default = lambda o: f"<<non-serializable: {type(o).__qualname__}>>"
  return json.dumps(obj, default=default)

obj = {"a": 1, "b": bytes()} # bytes is non-serializable by default
print(safe_serialize(obj))
Run Code Online (Sandbox Code Playgroud)

这将产生这样的结果:

{"a": 1, "b": "<<non-serializable: bytes>>"}
Run Code Online (Sandbox Code Playgroud)

此代码将打印类型名称,如果您想稍后实现自定义序列化程序,这可能很有用。