Python custom json сераиализатор

Иногда хочется чтобы в качестве объекта передачи данных был удобный класс но не хочется (или нет возможности) писать сераиализатор в JSON для него. Идеально было бы сделать класс, который сам умел бы сериализоваться в JSON дефолтным модулем без указания дополнительных сериализаторов.
Как это сделать?

Стандартный модуль 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):
if key not in [‘index’, ‘name’]:
self[‘meta’][key] = value
else:
super(Item, self).setitem(key, value)

Ну а главная особенность этого класса в том, что он легко понимается стандартным модулем json (конечно, если значения ключей это тоже стандартные типы или наследованные от них)

>>> json.dumps(item)
‘{“index”: 2, “name”: “item name”}’

Самый очевидный пример, использование в библиотеке requests. Передавая объект в аргумент json, мы не можем повлиять (https://github.com/psf/requests/blob/master/requests/models.py#L469) на его сериализацию, то есть добавить класс-сериализатор. Там ожидается готовый словарь или иной совместимый тип.

import requests
requests.post(url, json=item)

А вот так выглядит альтернатива с методом toJson()

requests.post(url, data=item.toJson(),
headers={‘Content-Type’: ‘application/json’})

А ещё ничто не мешает нам в любой момент конвертнуть наш класс в обычный словарь

>>> dict(item)
{“index”: 2, “name”: “item name”}

Не спорю, возможно есть более “умные” решения этой задачи 🤓 Но, тем не менее, аналогичным образом работает стандартный collections.namedtuple (https://docs.python.org/3/library/collections.html#collections.namedtuple). Он тоже наследуется от стандартного tuple, расширяет его функционал и сериализуется без дополнительных средств.

Для более сложных случаев можно посмотреть на библиотеку dataclasses-json (https://github.com/lidatong/dataclasses-json), которая поможет сериализовать dataclasses (https://docs.python.org/3/library/dataclasses.html).

Обсуждение закрыто.