Как это сделать?
Стандартный модуль JSON умеет правильно сериализовать стандартные типы. Но нам нужен кастомный класс с удобными методами и свойствами. Ответ очевиден - наследуемся от стандартного типа!
В качестве базового типа возьмем словарь. Например, мне нужен класс некоего абстрактного элемента списка с именем и индексом. Тогда реализация может быть такой.
class Item(dict):Я добавил два свойства с простым переназначением данных в словарь. Но предполагается, что там будут проверки значений и иные вычисления. Еще хорошо бы добавить методы
def __init__(self, name, index=0):
super().__init__(name=name, index=index)
@property
def name(self):
return self['name']
@name.setter
def name(self, value):
self['name'] = value
@property
def index(self):
return self['index']
@index.setter
def index(self, value):
self['index'] = value
def __setitem__(self, key, value):
if key not in ['index', 'name']:
raise KeyError
super().__setitem__(key, value)
__str__ и __repr__.>>> it = Item('item name')
>>> it.name
'item name'
>>> it.index
0
Также можно добавить любые методы классу. Например я переопределил __setitem__ чтобы нельзя было задать иные ключи.>>> it['key'] = 123Хотя, можно сделать как-то иначе. К примеру, все отличные от основных ключи записывать в отдельный ключ.
KeyError
def __setitem__(self, key, value):Ну а главная особенность этого класса в том, что он легко понимается стандартным модулем json (конечно, если значения ключей это тоже стандартные типы или наследованные от них)
if key not in ['index', 'name']:
self['meta'][key] = value
else:
super(Item, self).setitem(key, value)
>>> json.dumps(item)Самый очевидный пример, использование в библиотеке requests. Передавая объект в аргумент json, мы не можем повлиять на его сериализацию, то есть добавить класс-сериализатор. Там ожидается готовый словарь или иной совместимый тип.
'{"index": 2, "name": "item name"}'
import requestsА вот так выглядит альтернатива с методом toJson()
requests.post(url, json=item)
requests.post(url, data=item.toJson(),А ещё ничто не мешает нам в любой момент конвертнуть наш класс в обычный словарь
headers={'Content-Type': 'application/json'})
>>> dict(item)Не спорю, возможно есть более "умные" решения этой задачи 🤓 Но, тем не менее, аналогичным образом работает стандартный collections.namedtuple. Он тоже наследуется от стандартного tuple, расширяет его функционал и сериализуется без дополнительных средств.
{"index": 2, "name": "item name"}
Для более сложных случаев можно посмотреть на библиотеку dataclasses-json, которая поможет сериализовать dataclasses.
#tricks #libs