Как сделать класс сериализуемым в JSON
Как сделать класс Python сериализуемым?
class FileItem:
def __init__(self, fname):
self.fname = fname
Попытка сериализовать в JSON:
>>> import json
>>> x = FileItem('/foo/bar')
>>> json.dumps(x)
TypeError: Object of type 'FileItem' is not JSON serializable
Кто-нибудь может помочь мне решить эту проблему? Я пытаюсь сериализовать экземпляр класса FileItem
в формат JSON, но получаю ошибку, что объект данного типа не может быть сериализован. Как мне это исправить?
5 ответ(ов)
В большинстве ответов предложено изменить вызов json.dumps(), что не всегда возможно или желательно (например, это может происходить внутри компонента фреймворка).
Если вы хотите иметь возможность вызывать json.dumps(obj) без изменений, то простым решением будет наследование от dict:
class FileItem(dict):
def __init__(self, fname):
dict.__init__(self, fname=fname)
f = FileItem('tasks.txt')
json.dumps(f) # Здесь ничего менять не нужно
Это работает, если ваш класс представляет собой простую структуру данных. Для более сложных ситуаций вы всегда можете явно задать ключи в вызове dict.__init__()
.
Это решение работает, потому что json.dumps()
проверяет, является ли объект одним из нескольких известных типов с помощью довольно непитоничного isinstance(value, dict)
. Поэтому, если вы действительно не хотите наследоваться от dict
, можно переопределить __class__
и некоторые другие методы для достижения аналогичного результата.
Вы можете обернуть процесс сериализации объекта в формате JSON в отдельный класс. Вот простой пример:
import json
class FileItem:
def __init__(self, fname: str) -> None:
self.fname = fname
def __repr__(self) -> str:
return json.dumps(self.__dict__)
Однако, еще более хорошим вариантом будет создание класса FileItem
, который будет наследоваться от протокола JsonSerializable
. Пример реализации:
import json
from typing import Protocol
class JsonSerializable(Protocol):
def to_json(self) -> str:
return json.dumps(self.__dict__)
def __repr__(self) -> str:
return self.to_json()
class FileItem(JsonSerializable):
def __init__(self, fname: str) -> None:
self.fname = fname
Давайте протестируем нашу реализацию:
>>> f = FileItem('/foo/bar')
>>> f.to_json()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # приведение к строке
'{"fname": "/foo/bar"}'
Таким образом, вы получите более чистую и гибкую архитектуру для обработки сериализации ваших объектов в формат JSON.
Я столкнулся с этой проблемой на днях и реализовал более общую версию кодировщика для объектов Python, который может обрабатывать вложенные объекты и наследуемые поля:
import json
import inspect
class ObjectEncoder(json.JSONEncoder):
def default(self, obj):
if hasattr(obj, "to_json"):
return self.default(obj.to_json())
elif hasattr(obj, "__dict__"):
d = dict(
(key, value)
for key, value in inspect.getmembers(obj)
if not key.startswith("__")
and not inspect.isabstract(value)
and not inspect.isbuiltin(value)
and not inspect.isfunction(value)
and not inspect.isgenerator(value)
and not inspect.isgeneratorfunction(value)
and not inspect.ismethod(value)
and not inspect.ismethoddescriptor(value)
and not inspect.isroutine(value)
)
return self.default(d)
return obj
Пример:
class C(object):
c = "NO"
def to_json(self):
return {"c": "YES"}
class B(object):
b = "B"
i = "I"
def __init__(self, y):
self.y = y
def f(self):
print("f")
class A(B):
a = "A"
def __init__(self):
self.b = [{"ab": B("y")}]
self.c = C()
print(json.dumps(A(), cls=ObjectEncoder, indent=2, sort_keys=True))
Результат:
{
"a": "A",
"b": [
{
"ab": {
"b": "B",
"i": "I",
"y": "y"
}
}
],
"c": {
"c": "YES"
},
"i": "I"
}
Это решение позволяет сериализовать объекты с учетом их вложенности и наследования, что делает его более универсальным для использования в различных сценариях.
Действительно простое решение в одну строку
import json
json.dumps(your_object, default=vars)
Конец!
Вот тестовое использование:
import json
from dataclasses import dataclass
@dataclass
class Company:
id: int
name: str
@dataclass
class User:
id: int
name: str
email: str
company: Company
company = Company(id=1, name="Example Ltd")
user = User(id=1, name="John Doe", email="[email protected]", company=company)
json.dumps(user, default=vars)
Вывод будет следующим:
{
"id": 1,
"name": "John Doe",
"email": "[email protected]",
"company": {
"id": 1,
"name": "Example Ltd"
}
}
Ваша проблема связана с тем, что стандартный модуль json
не знает, как сериализовать объекты вашего класса User
в JSON. В примере вы используете библиотеку simplejson
, которая может работать с объектами непосредственно, но стандартный json
требует, чтобы вы предоставили ему способ преобразования объектов в сериализуемый формат.
Для этого нужно определить функцию default
, которая будет отвечать за преобразование вашего объекта в словарь. Вот как это можно сделать:
import json
class User(object):
def __init__(self, name, mail):
self.name = name
self.mail = mail
def _asdict(self):
return self.__dict__
def default(o):
return o._asdict()
print(json.dumps(User('alice', '[email protected]'), default=default))
Таким образом, когда вы вызываете json.dumps
, если встречается объект, который не может быть сериализован по стандартным правилам, Python будет использовать вашу функцию default
для его преобразования. В этой функции вы можете вызывать метод _asdict()
, чтобы вернуть атрибуты объекта в виде словаря, который уже может быть сериализован в JSON.
Преобразование данных формы в объект JavaScript с помощью jQuery
Как обойти "datetime.datetime не сериализуем в JSON"?
Почему Python не может разобрать эти данные JSON? [закрыто]
Как записать данные JSON в файл?
Как отправить JSON-данные с помощью Python Requests?