我想通过自定义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子类化?
答案 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?