Глобальная JSON-сериализация UUID и других типов с помощью psycopg2 и django
Я разрабатываю приложение с помощью Django 4.2.10. Я работаю с базой данных postgres и использую psycopg2. В моем приложении я работаю с вложенными словарями (выходящими из схем Django ninja/pydantic) и хотел бы записать их в JSONField
поля django. Однако они содержат UUID, что приводит к следующей ошибке:
TypeError: Object of type UUID is not JSON serializable
Для решения этой проблемы мне очень нужен "глобальный" хук/адаптер, который убедит psycopg2 правильно сериализовать UUID и другие типы, чтобы мне не приходилось беспокоиться об этом во всех местах, где я записываю эти данные в базу. Это должно работать для прямых/сырых запросов, а также через Django ORM.
Я нашел этот раздел JSON Adaptation в документации psycopg2.
Примечание Вы можете использовать
register_adapter()
для адаптации любого словаря Python к JSON, либо зарегистрировав Json или любой подкласс или фабрику, создающую совместимый адаптер:psycopg2.extensions.register_adapter(dict, psycopg2.extras.Json)
Однако эта настройка является глобальной, поэтому она не совместима с аналогичными адаптерами, например, с тем, который зарегистрированregister_hstore()
. Любой другой объект, поддерживаемый JSON, может быть зарегистрирован таким же образом, но при этом будет нарушено правило адаптации по умолчанию, так что будьте осторожны с нежелательными побочными эффектами.
Поэтому я подумал, что если я создам подкласс psycopg2.extras.Json
, то смогу переопределить поведение сериализации. Я проделал следующий эксперимент:
# my django model
class SomeModel(models.Model):
name = models.CharField(max_length=50)
allow = models.JSONField(default=list, null=False, blank=True)
# my test code
from uuid import uuid4
import orjson
from app.models import SomeModel
from psycopg2.extras import Json
from psycopg2.extensions import adapt, register_adapter, AsIs
from django.db import connection
def json_default(o):
# This allows me to easily add support for other types!
match o:
case set():
return list(o)
case UUID():
return str(o)
return str
class MyJson(Json):
def dumps(self, obj):
# print for debugging
print(obj)
return orjson.dumps(obj, default=json_default)
# register my custom adapter
register_adapter(dict, MyJson)
# test data
mydict = {"a": "b", "c": uuid4()}
# Raw query adapts UUID correctly!
with connection.cursor() as cursor:
cursor.execute("INSERT INTO app_somemodel (name, allow) VALUES (%s, %s)",
("a", mydict))
# This fails: TypeError: Object of type UUID is not JSON serializable
SomeModel.objects.create(name="a", allow=mydict)
Судя по трассировке, Django просто отказывается вызывать мой объект MyJson
и использует объект по умолчанию по адресу psycopg2/_json.py
:
Я в полной растерянности. Как это возможно, что Django полностью игнорирует мой адаптер и продолжает выполнять адаптер по умолчанию? Любая помощь будет очень признательна.