我如何json序列化自定义可迭代项?

时间:2018-11-07 16:45:17

标签: python json python-3.x types

我想创建一个表现为命名元组的类型,除了它具有自定义表示形式外,当序列化为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)

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

在没有将MyPair设为Tuple或在替换所有MyPair对象的预处理步骤中遍历整个文档的情况下,是否有任何一种不错的方法或不错的方法来实现此目的通过字符串?

编辑:为了解决Joran的答案,我仍然想保留对偶尔包含MyPair的复杂树进行序列化的功能。我的最小示例可能并没有说明清楚,抱歉。

2 个答案:

答案 0 :(得分:2)

仅包含默认参数

def my_class_encoder(o):
    if isinstance(o,MyClass):
       return repr(o)

json.dumps(myClassInstance,default=my_class_encoder)

比真正的编码器更容易处理

....

但实际上只是在您的课程中添加一个定义

class MyPair(NamedTuple):
    left: str
    right: str
    def serialize(self):
        return list(self)
    def __repr__(self):
        return self.left + ':' + self.right

然后是

 json.dumps(myClassInstance.serialize())

这样做的好处是可以更加清楚自己的操作(至少是恕我直言)

答案 1 :(得分: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)

对于自定义类型,将类型名称和将其字符串化的函数添加到self._serializers中,您应该会很好。 iterencode()的行为与正常行为不同(主要是因为它分开产生括号,而不是与第一个或最后一个元素并排出现),但是我看不到这会破坏任何地方。