如何 json 序列化自定义可迭代对象?

Chr*_*ian 5 python json types python-3.x

我想创建一个行为类似于命名元组的类型,只不过它具有自定义表示形式,在序列化为 JSON 时也受到尊重。

天真的按书本方法是这样的:

from typing import NamedTuple
import json


class MyPair(NamedTuple):
    left: str
    right: str

    def __repr__(self):
        return self.left + ':' + self.right


class MyJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, MyPair):
            return str(obj)
        return json.JSONEncoder.default(self, obj)
Run Code Online (Sandbox Code Playgroud)

现在将按预期print(MyPair('a', 'b'))输出,但会产生,因为仅当对象不能原始序列化为 JSON 时才被调用。由于我自己的类型是一个元组,因此在我有机会干预之前它将被序列化。a:bprint(json.dumps([MyPair('a', 'b')], cls=MyJSONEncoder))[["a", "b"]]default()

是否有任何好的或不太好的方法来实现此目的,而无需在用字符串替换所有对象的预处理步骤中MyPair不进行或迭代整个文档?TupleMyPair

编辑:为了解决 Joran 的答案,我仍然想保留序列化仅包含偶尔MyPairs 的复杂树的能力。我的最小例子可能没有说清楚,抱歉。

Chr*_*ian 0

所以我最终或多或少地从头开始重新实现了 JSONEncoder。因为我不需要任何花哨的漂亮打印,所以这相当简单:

class MyJSONEncoder(json.JSONEncoder):

    def __init__(self, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False,
                 indent=None, separators=None, default=None):
        super().__init__(skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular,
                         allow_nan=allow_nan, sort_keys=sort_keys, indent=indent, separators=separators,
                         default=default)
        self._serializers: Set[Tuple[Type, Callable]] = {
            (MyPair, lambda pair: '"' + str(pair) + '"',)
        }

    def default(self, o):
        return super().default(o)

    def encode(self, o):
        return ''.join(self.iterencode(o))

    def iterencode(self, o, _one_shot=False):
        for t, serializer in self._serializers:
            if isinstance(o, t):
                yield serializer(o)
                break
        else:
            if isinstance(o, bool):
                yield "true" if o else "false"
            elif isinstance(o, str):
                yield '"' + o + '"'
            elif isinstance(o, bytes):
                yield '"' + o.decode("utf-8") + '"'
            elif isinstance(o, int) or isinstance(o, float) or isinstance(o, Decimal):
                yield str(o)
            elif isinstance(o, Dict):
                yield '{'
                for num, (key, value) in enumerate(o.items()):
                    yield bool(num) * ', ' + '"' + str(key) + '": '
                    yield from self.iterencode(value)
                yield '}'
            elif isinstance(o, Sequence):
                yield '['
                for num, value in enumerate(o):
                    yield bool(num) * ', '
                    yield from self.iterencode(value)
                yield ']'
            else:
                yield self.default(o)
Run Code Online (Sandbox Code Playgroud)

对于自定义类型,添加类型名称和将其字符串化的函数self._serializers就可以了。其iterencode()行为与普通的不同(主要是它单独产生括号,而不是与第一个或最后一个元素并排),但我看不出这会在哪里破坏任何东西。