python json模块和子类字典

时间:2018-07-02 19:03:24

标签: python json dictionary serialization subclassing

我想通过自定义JSON序列化定义dict的子类。我遇到的问题是,如果我直接对dict进行子类化,那么在遇到我的dict子类的实例时,json模块不会进入“默认”功能,从而绕过了我的自定义序列化。考虑:

import collections


class MyDictA(dict):
    # subclass of dict

    def to_json(self):
        return {
            "items": dict(self),
            "_type": self.__module__ + "." + self.__class__.__name__,
        }

    def __repr__(self):
        return self.__class__.__name__ + "(" + repr(dict(self)) + ")"


class MyDictB(collections.MutableMapping):
    # behaves like a dict, is not a dict
    # can easily implement the remaining dict methods

    def __getitem__(self, item):
        return self.__dict__[item]

    def __setitem__(self, key, value):
        self.__dict__[key] = value

    def to_json(self):
        return {
            "items": vars(self),
            "_type": self.__module__ + "." + self.__class__.__name__,
        }

    def __repr__(self):
        return self.__class__.__name__ + "(" + repr(self.__dict__) + ")"

    def __iter__(self):
        return self.__dict__.__iter__()

    def __len__(self):
        return len(self.__dict__)

    def __delitem__(self, key):
        del self.__dict__[key]

我可以轻松实现其余的dict方法,因此MyDictB是dict的直接替代品,但是从某种意义上来说,这是非Python的。

现在,我们实现自定义序列化:

import json

def my_default(obj):
    if hasattr(obj, "to_json"):
        return obj.to_json()
    else:
        return obj

示例:

A = MyDictA()
A["foo"] = "bar"

B = MyDictB()
B["foo"] = "bar"

结果:

>>> print(A)
MyDictA({'foo': 'bar'})
>>> print(B)
MyDictB({'foo': 'bar'})
>>> print(jsonA)
{"foo": "bar"}
>>> print(jsonB)
{"_type": "__main__.MyDictB", "items": {"foo": "bar"}}

如您所见,只有MyDictB通过自定义序列化“ my_default”; MyDictA实例永远不会做,因为它们是dict实例。

json模块中的问题是它以isinstance(obj,dict)为条件,请参阅json / encoder.py中“ _iterencode”的实现。

注意:

>>> isinstance(A, collections.Mapping)
True
>>> isinstance(B, collections.Mapping)
True
>>> isinstance(A, dict)
True
>>> isinstance(B, dict)
False

是否有更好的方法让json模块尊重我的dict子类化?

1 个答案:

答案 0 :(得分:0)

部分解决方案:

jsonA_1 = json.dumps(A, default=my_default)
jsonB_1 = json.dumps(B, default=my_default)

def my_isinstance(obj, A_tuple):
    if isinstance(obj, MyDictA):
        if A_tuple==dict:
            return False
        if isinstance(A_tuple, collections.Iterable):
            return any(my_isinstance(obj, A) for A in A_tuple)
    return isinstance(obj, A_tuple)

# override isinstance default in _make_iterencode
_make_iterencode_defaults = list(json.encoder._make_iterencode.__defaults__)
_make_iterencode_defaults[5] = my_isinstance
json.encoder._make_iterencode.__defaults__ = tuple(_make_iterencode_defaults)
# turn off c_make_encoder
json.encoder.c_make_encoder = None

assert isinstance(A, dict) is True
assert isinstance(B, dict) is False
assert my_isinstance(A, dict) is False
assert my_isinstance(B, dict) is False
assert my_isinstance(A, (dict, MyDictB)) is False
assert my_isinstance(A, (dict, MyDictA)) is True

# let's try that again:
jsonA_2 = json.dumps(A, default=my_default)
jsonB_2 = json.dumps(B, default=my_default)

结果:

>>> jsonA_1
'{"foo": "bar"}'
>>> jsonB_1
'{"items": {"foo": "bar"}, "_type": "__main__.MyDictB"}'
>>> jsonA_2
'{"items": {"foo": "bar"}, "_type": "__main__.MyDictA"}'
>>> jsonB_2
'{"items": {"foo": "bar"}, "_type": "__main__.MyDictB"}'

这似乎可行,除了它需要禁用c_make_encoder,这大概是一个更快的实现。

编辑:

类似的解决方案 How to change json encoding behaviour for serializable python object?