Как сделать класс JSON сериализуемым
Как сделать класс Python сериализуемым?
Простой класс:
class FileItem:
def __init__(self, fname):
self.fname = fname
Что я должен сделать, чтобы получить вывод:
json.dumps()
Без ошибки (FileItem instance at ... is not JSON serializable
)
43 ответа
У вас есть представление об ожидаемом выходе? Например, это будет делать?
>>> f = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'
В этом случае вы можете просто позвонить json.dumps(f.__dict__)
,
Если вы хотите более индивидуализированный вывод, то вам придется подкласс JSONEncoder
и реализовать свою собственную сериализацию.
Для тривиального примера см. Ниже.
>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
def default(self, o):
return o.__dict__
>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'
Затем вы передаете этот класс в json.dumps()
метод как cls
kwarg:
json.dumps(cls=MyEncoder)
Если вы также хотите декодировать, вам придется предоставить object_hook
к JSONDecoder
учебный класс. Например,
>>> def from_json(json_object):
if 'fname' in json_object:
return FileItem(json_object['fname'])
>>> f = JSONDecoder(object_hook = from_json).decode('{"fname": "/foo/bar"}')
>>> f
<__main__.FileItem object at 0x9337fac>
>>>
Вот простое решение для простой функции:
.toJSON()
метод
Вместо сериализуемого класса JSON реализуйте метод сериализатора:
import json
class Object:
def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
Так что вы просто вызываете его для сериализации:
me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"
print(me.toJSON())
будет выводить:
{
"age": 35,
"dog": {
"name": "Apollo"
},
"name": "Onur"
}
Для более сложных классов вы можете рассмотреть инструмент jsonpickle:
jsonpickle - это библиотека Python для сериализации и десериализации сложных объектов Python в JSON и из него.
Стандартные библиотеки Python для кодирования Python в JSON, такие как json, simplejson и demjson в stdlib, могут обрабатывать только примитивы Python, имеющие прямой эквивалент JSON (например, dicts, списки, строки, целые числа и т. Д.). jsonpickle основывается на этих библиотеках и позволяет сериализовать более сложные структуры данных в JSON. jsonpickle легко настраивается и расширяется, что позволяет пользователю выбирать JSON-бэкэнд и добавлять дополнительные бэкэнды.
Большинство ответов включают изменение вызова 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) #No need to change anything here
Это работает, если ваш класс является просто базовым представлением данных, для более сложных вещей вы всегда можете установить ключи явно.
Как упоминалось во многих других ответах, вы можете передать функцию в
json.dumps
для преобразования объектов, которые не являются одним из типов, поддерживаемых по умолчанию, в поддерживаемый тип. Удивительно, но ни один из них не упоминает простейший случай - использование встроенной функции. vars
для преобразования объектов в dict, содержащий все их атрибуты:
json.dumps(obj, default=vars)
Если вам нужна более конкретная сериализация для определенных типов (например, исключение определенных атрибутов), вы можете использовать настраиваемую функцию или
JSONEncoder
как описано в других ответах.
Просто добавь to_json
метод для вашего класса, как это:
def to_json(self):
return self.message # or how you want it to be serialized
И добавьте этот код (из этого ответа), где-нибудь наверху всего:
from json import JSONEncoder
def _default(self, obj):
return getattr(obj.__class__, "to_json", _default.default)(obj)
_default.default = JSONEncoder().default
JSONEncoder.default = _default
При импорте модуль json будет исправлен, поэтому JSONEncoder.default() автоматически проверяет наличие специального метода to_json() и использует его для кодирования объекта, если он найден.
Как сказал Онур, но на этот раз вам не нужно обновлять каждый json.dumps()
в вашем проекте.
Настоящий ответ на вопрос "сделать _ Class _ сериализуемым"
_
TL; DR: копирование и вставка из варианта 2
- Во-первых, к сожалению, нет: "официального" решения для Python не существует.
- Под официальным решением я имею в виду, что (по состоянию на 2021 год) нет возможности добавить метод в класс (например,
toJSON
в JavaScript) или способ зарегистрировать свой класс в модуле json, который будет автоматически вызываться, когдаjson.dumps([1,2, your_obj])
выполняется. Я не уверен, почему это не объясняется никакими ответами. - Наиболее близким официальным подходом, вероятно, является ответ Эндихасита, который заключается в наследовании словаря. Однако наследование из словаря не очень хорошо работает для многих пользовательских классов, таких как AdvancedDateTime или тензоры pytorch.
- Под официальным решением я имею в виду, что (по состоянию на 2021 год) нет возможности добавить метод в класс (например,
- Однако есть обходной путь без предостережений:
- Мутировать
json.dumps
(влияет везде, даже на модули pip, которые импортируют json) - Добавлять
def __json__(self)
метод к вашему классу
- Мутировать
_
Вариант 1. Позволить модулю выполнить исправление
(расширенная + упакованная версия ответа Fancy John , спасибо @FancyJohn)
pip install json-fix
some_file_thats_imported_before_your_class_definitions.py
# Step 1: patch json.dumps, only needs to run once per runtime
from json_fix import fix_it; fix_it()
your_class_definition.py
# Step 2
class YOUR_CLASS:
def __json__(self):
# YOUR CUSTOM CODE HERE
# you probably just want to do:
# return self.__dict__
return "a built-in object that is natually json-able"
_
Вариант 2: патчить json.dumps самостоятельно
Примечание : этот подход упрощен и не учитывает управление поведением json для внешних классов (массивы numpy, datetime, dataframes, тензоры и т. Д.), Что и обрабатывает модуль pip (в варианте 1).
some_file_thats_imported_before_your_class_definitions.py
# Step: 1
# create the patch
from json import JSONEncoder
def wrapped_default(self, obj):
return getattr(obj.__class__, "__json__", wrapped_default.default)(obj)
wrapped_default.default = JSONEncoder().default
# apply the patch
JSONEncoder.original_default = JSONEncoder.default
JSONEncoder.default = wrapped_default
your_class_definition.py
# Step 2
class YOUR_CLASS:
def __json__(self, **options):
# YOUR CUSTOM CODE HERE
# you probably just want to do:
# return self.__dict__
return "a built-in object that is natually json-able"
_
Все остальные ответы кажутся «Лучшие практики / подходы к сериализации настраиваемого объекта»
- Что уже описано здесь, в документации (введите "сложный" пример кодирования комплексных чисел)
Мне нравится ответ Онура, но я бы добавил, чтобы toJSON()
Метод для объектов для сериализации себя:
def dumper(obj):
try:
return obj.toJSON()
except:
return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
Если вы используете Python3.5+, вы можете использовать jsons
, Он преобразует ваш объект (и все его атрибуты рекурсивно) в диктовку.
import jsons
a_dict = jsons.dump(your_object)
Или, если вы хотите строку:
a_str = jsons.dumps(your_object)
Или если ваш класс реализован jsons.JsonSerializable
:
a_dict = your_object.json
Другой вариант - обернуть дамп JSON в свой собственный класс:
import json
class FileItem:
def __init__(self, fname):
self.fname = fname
def __repr__(self):
return json.dumps(self.__dict__)
Или, что еще лучше, создание подкласса класса FileItem из JsonSerializable
учебный класс:
import json
class JsonSerializable(object):
def toJson(self):
return json.dumps(self.__dict__)
def __repr__(self):
return self.toJson()
class FileItem(JsonSerializable):
def __init__(self, fname):
self.fname = fname
Тестирование:
>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'
Я столкнулся с этой проблемой на днях и реализовал более общую версию Encoder для объектов 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=lambda __o: __o.__dict__)
Конец!
То, что идет ниже, является тестом.
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="john@doe.net", company=company)
json.dumps(user, default=lambda __o: __o.__dict__)
Выход:
{
"id": 1,
"name": "John Doe",
"email": "john@doe.net",
"company": {
"id": 1,
"name": "Example Ltd"
}
}
import simplejson
class User(object):
def __init__(self, name, mail):
self.name = name
self.mail = mail
def _asdict(self):
return self.__dict__
print(simplejson.dumps(User('alice', 'alice@mail.com')))
если использовать стандарт json
Вам нужно определить default
функция
import json
def default(o):
return o._asdict()
print(json.dumps(User('alice', 'alice@mail.com'), default=default))
json
ограничено с точки зрения объектов, которые он может печатать, и jsonpickle
(вам может понадобиться pip install jsonpickle
) ограничен с точки зрения не может отступать текст. Если вы хотите проверить содержимое объекта, класс которого вы не можете изменить, я все равно не смог бы найти более прямой путь, чем:
import json
import jsonpickle
...
print json.dumps(json.loads(jsonpickle.encode(object)), indent=2)
Обратите внимание, что они по-прежнему не могут печатать методы объекта.
Самый простой ответ
class Object(dict):
def __init__(self):
pass
def __getattr__(self, key):
return self[key]
def __setattr__(self, key, value):
self[key] = value
# test
obj = Object()
obj.name = "John"
obj.age = 25
obj.brothers = [ Object() ]
text = json.dumps(obj)
Теперь он дает вам результат, ничего не меняйте в json.dumps(...)
'{"name": "John", "age": 25, "brothers": [{}]}'
Чтобы бросить еще один бревно в этот 11-летний пожар, мне нужно решение, отвечающее следующим критериям:
- Позволяет сериализовать экземпляр класса FileItem, используя только
- Позволяет экземплярам FileItem иметь свойства: fileItem.fname
- Позволяет передавать экземпляры FileItem в любую библиотеку, которая будет сериализовать их, используя
- Не требует передачи каких-либо других полей в
json.dumps
(как пользовательский сериализатор)
IE:
fileItem = FileItem('filename.ext')
assert json.dumps(fileItem) == '{"fname": "filename.ext"}'
assert fileItem.fname == 'filename.ext'
Мое решение:
- Унаследовать класс obj от
- Сопоставьте каждое свойство объекта с нижележащим
dict
class FileItem(dict):
def __init__(self, fname):
self['fname'] = fname
#fname property
fname: str = property()
@fname.getter
def fname(self):
return self['fname']
@fname.setter
def fname(self, value: str):
self['fname'] = value
#Repeat for other properties
Да, это несколько утомительно, если у вас много свойств, но это JSONSerializable, и он ведет себя как объект, и вы можете передать его любой библиотеке, которая будет
json.dumps(obj)
Это.
Этот класс может сделать свое дело, он конвертирует объект в стандартный JSON.
import json
class Serializer(object):
@staticmethod
def serialize(object):
return json.dumps(object, default=lambda o: o.__dict__.values()[0])
использование:
Serializer.serialize(my_object)
работает в python2.7
а также python3
,
Вот мои 3 цента...
Это демонстрирует явную сериализацию JSON для древовидного объекта Python.
Примечание. Если вам действительно нужен какой-то подобный код, вы можете использовать витой класс FilePath.
import json, sys, os
class File:
def __init__(self, path):
self.path = path
def isdir(self):
return os.path.isdir(self.path)
def isfile(self):
return os.path.isfile(self.path)
def children(self):
return [File(os.path.join(self.path, f))
for f in os.listdir(self.path)]
def getsize(self):
return os.path.getsize(self.path)
def getModificationTime(self):
return os.path.getmtime(self.path)
def _default(o):
d = {}
d['path'] = o.path
d['isFile'] = o.isfile()
d['isDir'] = o.isdir()
d['mtime'] = int(o.getModificationTime())
d['size'] = o.getsize() if o.isfile() else 0
if o.isdir(): d['children'] = o.children()
return d
folder = os.path.abspath('.')
json.dump(File(folder), sys.stdout, default=_default)
Харако дал довольно аккуратный ответ. Мне нужно было исправить некоторые мелочи, но это работает:
Код
# Your custom class
class MyCustom(object):
def __json__(self):
return {
'a': self.a,
'b': self.b,
'__python__': 'mymodule.submodule:MyCustom.from_json',
}
to_json = __json__ # supported by simplejson
@classmethod
def from_json(cls, json):
obj = cls()
obj.a = json['a']
obj.b = json['b']
return obj
# Dumping and loading
import simplejson
obj = MyCustom()
obj.a = 3
obj.b = 4
json = simplejson.dumps(obj, for_json=True)
# Two-step loading
obj2_dict = simplejson.loads(json)
obj2 = MyCustom.from_json(obj2_dict)
# Make sure we have the correct thing
assert isinstance(obj2, MyCustom)
assert obj2.__dict__ == obj.__dict__
Обратите внимание, что нам нужно два шага для загрузки. На данный момент, __python__
свойство не используется.
Насколько это распространено?
Используя метод AlJohri, я проверяю популярность подходов:
Сериализация (Python -> JSON):
to_json
: 266 595 на 2018-06-27toJSON
: 96 307 на 2018-06-27__json__
: 8,504 на 2018-06-27for_json
: 6,937 на 2018-06-27
Десериализация (JSON -> Python):
from_json
: 226 101 на 2018-06-27
Это хорошо сработало для меня:
class JsonSerializable(object):
def serialize(self):
return json.dumps(self.__dict__)
def __repr__(self):
return self.serialize()
@staticmethod
def dumper(obj):
if "serialize" in dir(obj):
return obj.serialize()
return obj.__dict__
а потом
class FileItem(JsonSerializable):
...
а также
log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))
import json
class Foo(object):
def __init__(self):
self.bar = 'baz'
self._qux = 'flub'
def somemethod(self):
pass
def default(instance):
return {k: v
for k, v in vars(instance).items()
if not str(k).startswith('_')}
json_foo = json.dumps(Foo(), default=default)
assert '{"bar": "baz"}' == json_foo
print(json_foo)
Ребята, зачем вы все так усложняете? Вот простой пример:
#!/usr/bin/env python3
import json
from dataclasses import dataclass
@dataclass
class Person:
first: str
last: str
age: int
@property
def __json__(self):
return {
"name": f"{self.first} {self.last}",
"age": self.age
}
john = Person("John", "Doe", 42)
print(json.dumps(john, indent=4, default=lambda x: x.__json__))
Таким образом, вы также можете сериализовать вложенные классы, как
__json__
возвращает объект Python, а не строку. Нет необходимости использовать
JSONEncoder
, как
default
параметр с простой лямбдой тоже работает нормально.
я использовал
@property
вместо простой функции, так как это кажется более естественным и современным.
@dataclass
также просто пример, он работает и для «нормального» класса.
Kyle Delaney, поэтому я попытался использовать ответ /questions/43384801/kak-sdelat-klass-json-serializuemyim/43384808#43384808, а также улучшенную версию /questions/9232647/kodirovanie-vlozhennogo-obekta-python-v-json/9232660#9232660
для создания миксина "JSONAble".
Итак, чтобы сделать класс JSON сериализуемым, используйте "JSONAble" в качестве суперкласса и либо вызовите:
instance.toJSON()
или
instance.asJSON()
для двух предложенных методов. Вы также можете расширить класс JSONAble другими предлагаемыми здесь подходами.
Пример теста для модульного теста с семейством и личностью дает следующие результаты:
toJSOn():
{
"members": {
"Flintstone,Fred": {
"firstName": "Fred",
"lastName": "Flintstone"
},
"Flintstone,Wilma": {
"firstName": "Wilma",
"lastName": "Flintstone"
}
},
"name": "The Flintstones"
}
asJSOn():
{'name': 'The Flintstones', 'members': {'Flintstone,Fred': {'firstName': 'Fred', 'lastName': 'Flintstone'}, 'Flintstone,Wilma': {'firstName': 'Wilma', 'lastName': 'Flintstone'}}}
Модульный тест с образцом семьи и человека
def testJsonAble(self):
family=Family("The Flintstones")
family.add(Person("Fred","Flintstone"))
family.add(Person("Wilma","Flintstone"))
json1=family.toJSON()
json2=family.asJSON()
print(json1)
print(json2)
class Family(JSONAble):
def __init__(self,name):
self.name=name
self.members={}
def add(self,person):
self.members[person.lastName+","+person.firstName]=person
class Person(JSONAble):
def __init__(self,firstName,lastName):
self.firstName=firstName;
self.lastName=lastName;
jsonable.py, определяющий миксин JSONAble
'''
Created on 2020-09-03
@author: wf
'''
import json
class JSONAble(object):
'''
mixin to allow classes to be JSON serializable see
https://stackru.com/questions/3768895/how-to-make-a-class-json-serializable
'''
def __init__(self):
'''
Constructor
'''
def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
def getValue(self,v):
if (hasattr(v, "asJSON")):
return v.asJSON()
elif type(v) is dict:
return self.reprDict(v)
elif type(v) is list:
vlist=[]
for vitem in v:
vlist.append(self.getValue(vitem))
return vlist
else:
return v
def reprDict(self,srcDict):
'''
get my dict elements
'''
d = dict()
for a, v in srcDict.items():
d[a]=self.getValue(v)
return d
def asJSON(self):
'''
recursively return my dict elements
'''
return self.reprDict(self.__dict__)
Если вы не против установить пакет для него, вы можете использовать json-tricks:
pip install json-tricks
После этого вам просто нужно импортировать dump(s)
от json_tricks
вместо JSON, и это обычно будет работать:
from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)
который даст
{
"__instance_type__": [
"module_name.test_class",
"MyTestCls"
],
"attributes": {
"attr": "val",
"dct_attr": {
"hello": 42
}
}
}
И это в основном все!
Это будет отлично работать в целом. Есть некоторые исключения, например, если в __new__
или больше волшебства метакласса происходит.
Очевидно, что загрузка также работает (иначе какой смысл):
from json_tricks import loads
json_str = loads(json_str)
Это предполагает, что module_name.test_class.MyTestCls
может быть импортирован и не изменился несовместимыми способами. Вы получите обратно экземпляр, а не какой-то словарь или что-то еще, и это должна быть копия, идентичная копии, которую вы сбросили.
Если вы хотите настроить сериализацию чего-либо (де), вы можете добавить специальные методы в ваш класс, например так:
class CustomEncodeCls:
def __init__(self):
self.relevant = 42
self.irrelevant = 37
def __json_encode__(self):
# should return primitive, serializable types like dict, list, int, string, float...
return {'relevant': self.relevant}
def __json_decode__(self, **attrs):
# should initialize all properties; note that __init__ is not called implicitly
self.relevant = attrs['relevant']
self.irrelevant = 12
который сериализует только часть параметров атрибутов, в качестве примера.
И в качестве бесплатного бонуса вы получаете (де) сериализацию массивов, даты и времени, упорядоченные карты, а также возможность добавлять комментарии в json.
Отказ от ответственности: я создал json_tricks, потому что у меня была та же проблема, что и у вас.
Jsonweb, кажется, лучшее решение для меня. Смотрите http://www.jsonweb.info/en/latest/
from jsonweb.encode import to_object, dumper
@to_object()
class DataModel(object):
def __init__(self, id, value):
self.id = id
self.value = value
>>> data = DataModel(5, "foo")
>>> dumper(data)
'{"__type__": "DataModel", "id": 5, "value": "foo"}'
Опираясь на Quinten Cabo"S ответ:
def sterilize(obj):
"""Make an object more ameniable to dumping as json
"""
if type(obj) in (str, float, int, bool, type(None)):
return obj
elif isinstance(obj, dict):
return {k: sterilize(v) for k, v in obj.items()}
list_ret = []
dict_ret = {}
for a in dir(obj):
if a == '__iter__' and callable(obj.__iter__):
list_ret.extend([sterilize(v) for v in obj])
elif a == '__dict__':
dict_ret.update({k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']})
elif a not in ['__doc__', '__module__']:
aval = getattr(obj, a)
if type(aval) in (str, float, int, bool, type(None)):
dict_ret[a] = aval
elif a != '__class__' and a != '__objclass__' and isinstance(aval, type):
dict_ret[a] = sterilize(aval)
if len(list_ret) == 0:
if len(dict_ret) == 0:
return repr(obj)
return dict_ret
else:
if len(dict_ret) == 0:
return list_ret
return (list_ret, dict_ret)
Отличия
- Работает для любой итерации, а не только
list
а такжеtuple
(работает для массивов NumPy и т. д.) - Работает для динамических типов (тех, которые содержат
__dict__
). - Включает собственные типы
float
а такжеNone
поэтому они не преобразуются в строку. - Классы, у которых есть
__dict__
и участники будут в основном работать (если__dict__
и имена участников конфликтуют, вы получите только один - скорее всего, член) - Классы, которые являются списками и имеют члены, будут выглядеть как кортеж из списка и словаря.
- Python3 (что
isinstance()
звонок может быть единственным, что нужно изменить)
class DObject(json.JSONEncoder):
def delete_not_related_keys(self, _dict):
for key in ["skipkeys", "ensure_ascii", "check_circular", "allow_nan", "sort_keys", "indent"]:
try:
del _dict[key]
except:
continue
def default(self, o):
if hasattr(o, '__dict__'):
my_dict = o.__dict__.copy()
self.delete_not_related_keys(my_dict)
return my_dict
else:
return o
a = DObject()
a.name = 'abdul wahid'
b = DObject()
b.name = a
print(json.dumps(b, cls=DObject))
Я придумал собственное решение. Используйте этот метод, передайте любой документ (dict,list, ObjectId и т. Д.) Для сериализации.
def getSerializable(doc):
# check if it's a list
if isinstance(doc, list):
for i, val in enumerate(doc):
doc[i] = getSerializable(doc[i])
return doc
# check if it's a dict
if isinstance(doc, dict):
for key in doc.keys():
doc[key] = getSerializable(doc[key])
return doc
# Process ObjectId
if isinstance(doc, ObjectId):
doc = str(doc)
return doc
# Use any other custom serializting stuff here...
# For the rest of stuff
return doc
Я столкнулся с этой проблемой, когда попытался сохранить модель Peewee в PostgreSQL. JSONField
,
После некоторой борьбы вот общее решение.
Ключом к моему решению является прохождение исходного кода Python и понимание того, что документация кода (описанная здесь) уже объясняет, как расширить существующий json.dumps
поддерживать другие типы данных.
Предположим, у вас есть модель, которая содержит некоторые поля, которые нельзя сериализовать в JSON, и модель, которая содержит поле JSON, изначально выглядит так:
class SomeClass(Model):
json_field = JSONField()
Просто определите обычай JSONEncoder
как это:
class CustomJsonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, SomeTypeUnsupportedByJsonDumps):
return < whatever value you want >
return json.JSONEncoder.default(self, obj)
@staticmethod
def json_dumper(obj):
return json.dumps(obj, cls=CustomJsonEncoder)
А потом просто используйте его в своем JSONField
как ниже:
class SomeClass(Model):
json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)
Ключ является default(self, obj)
метод выше. Для каждого ... is not JSON serializable
жалобу, которую вы получаете от Python, просто добавьте код для обработки типа unserializable-to-JSON (например, Enum
или же datetime
)
Например, вот как я поддерживаю класс, наследующий от Enum
:
class TransactionType(Enum):
CURRENT = 1
STACKED = 2
def default(self, obj):
if isinstance(obj, TransactionType):
return obj.value
return json.JSONEncoder.default(self, obj)
Наконец, с помощью кода, реализованного, как указано выше, вы можете просто преобразовать любые модели Peewee в объект с поддержкой JSON, как показано ниже:
peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)
Хотя приведенный выше код был (несколько) специфичен для Peewee, но я думаю:
- Это применимо к другим ORM (Django и т. Д.) В целом
- Кроме того, если вы поняли, как
json.dumps
работает, это решение также работает с Python (без ORM) в целом тоже
Любые вопросы, пожалуйста, оставляйте в разделе комментариев. Спасибо!
Мне больше всего понравился метод Lost Koder. Я столкнулся с проблемами при попытке сериализовать более сложные объекты, члены / методы которых не сериализуемы. Вот моя реализация, которая работает на большем количестве объектов:
class Serializer(object):
@staticmethod
def serialize(obj):
def check(o):
for k, v in o.__dict__.items():
try:
_ = json.dumps(v)
o.__dict__[k] = v
except TypeError:
o.__dict__[k] = str(v)
return o
return json.dumps(check(obj).__dict__, indent=2)