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

新手上路,请多包涵

我正在尝试使用 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
}

现在,调用 json.dumps(packet) 显然会导致类型错误:

 ...

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

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

I got the code to work by JSONEncoder and returning None for fields of BytesIO type, but that means my serialized string contains loads of "_io": null 元素,我根本不想拥有这些元素……

原文由 mz8i 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.3k
2 个回答

带有前导 _ 下划线的键并不是真正的“隐藏”,它们只是 JSON 的更多字符串。 Construct Container 类只是一个带顺序的字典, _io 键对该类来说没有什么特别之处。

你有两个选择:

  • 实现一个 default 钩子,它只返回一个替换值。
  • 在序列化 之前 过滤掉你知道不能工作的键值对。

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

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

 json.dumps(packet, default=lambda o: '<not serializable>')

可以递归地进行过滤; @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

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

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

原文由 Martijn Pieters 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

如果您真的不需要排除该字段,那么您可以生成一个默认值:

 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))

这将产生这样的结果:

 {"a": 1, "b": "<<non-serializable: bytes>>"}

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

原文由 David Rissato Cruz 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题