Python JSON 模块和子类化字典



我想定义一个字典的子类,特别是自定义JSON序列化。我遇到的问题是,如果我直接子类字典,那么 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的直接替代品,但这在某种程度上感觉不是pythonic。

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

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的实例永远不会这样做,因为它们是字典实例。

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 模块尊重我的字典子类?

部分解决方案:

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,这可能是一个更快的实现。

编辑:

类似的解决方案 如何更改可序列化 python 对象的 json 编码行为?

最新更新