JSON-модуль Python и подкласс - PullRequest
0 голосов
/ 02 июля 2018

Я хотел бы определить подкласс dict, в частности, с настраиваемой сериализацией JSON. Проблема, с которой я сталкиваюсь, заключается в том, что, если я непосредственно создаю подкласс dict, то модуль json не входит в функцию 'default' при обнаружении экземпляров моего подкласса dict, минуя мою собственную сериализацию. Рассмотрим:

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, но это почему-то кажется непитоническим.

Теперь мы реализуем пользовательскую сериализацию:

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), см. Реализацию "_iterencode" в json / encoder.py.

Примечание:

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

Есть ли лучший способ заставить модуль json уважать мой подкласс dict?

1 Ответ

0 голосов
/ 02 июля 2018

Частичное решение:

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, что, вероятно, является более быстрой реализацией.

Edit:

Похожие решения в Как изменить поведение кодирования json для сериализуемого объекта python?

...