Сериализация экземпляра класса в JSON
Я пытаюсь создать строковое представление JSON экземпляра класса и с трудом. Скажем, класс построен таким образом:
class testclass:
value1 = "a"
value2 = "b"
Вызов json.dumps выполняется следующим образом:
t = testclass()
json.dumps(t)
Это не работает и говорит мне, что тестовый класс не является сериализуемым JSON.
TypeError: <__main__.testclass object at 0x000000000227A400> is not JSON serializable
Я также пробовал использовать модуль рассола:
t = testclass()
print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))
И он предоставляет информацию об экземпляре класса, но не сериализованный контент экземпляра класса.
b'\x80\x03c__main__\ntestclass\nq\x00)\x81q\x01}q\x02b.'
Что я делаю неправильно?
Ответы
Ответ 1
Основная проблема заключается в том, что JSON-кодировщик json.dumps()
знает, как по умолчанию сериализовать ограниченный набор типов объектов, все встроенные типы. Список здесь: https://docs.python.org/3.3/library/json.html#encoders-and-decoders
Одним хорошим решением было бы сделать ваш класс наследуемым от JSONEncoder
а затем реализовать JSONEncoder.default()
и заставить эту функцию генерировать правильный JSON для вашего класса.
Простым решением было бы вызвать json.dumps()
для члена .__dict__
этого экземпляра. Это стандартный Python dict
и если ваш класс прост, он будет сериализуем JSON.
class Foo(object):
def __init__(self):
self.x = 1
self.y = 2
foo = Foo()
s = json.dumps(foo) # raises TypeError with "is not JSON serializable"
s = json.dumps(foo.__dict__) # s set to: {"x":1, "y":2}
Вышеупомянутый подход обсужден в этой публикации блога:
Сериализация произвольных объектов Python в JSON с использованием __dict__
Ответ 2
Там один способ, который отлично подходит для меня, что вы можете попробовать:
json.dumps()
может принимать необязательный параметр по умолчанию, где вы можете указать пользовательскую функцию сериализатора для неизвестных типов, что в моем случае выглядит как
def serialize(obj):
"""JSON serializer for objects not serializable by default json code"""
if isinstance(obj, date):
serial = obj.isoformat()
return serial
if isinstance(obj, time):
serial = obj.isoformat()
return serial
return obj.__dict__
Первые два ifs предназначены для сериализации даты и времени
а затем возвращается obj.__dict__
для любого другого объекта.
окончательный вызов выглядит следующим образом:
json.dumps(myObj, default=serialize)
Это особенно полезно при сериализации коллекции, и вы не хотите явно вызывать __dict__
для каждого объекта. Здесь это делается для вас автоматически.
До сих пор работало так хорошо для меня, с нетерпением ожидая ваших мыслей.
Ответ 3
Вы можете указать именованный параметр по default
в функции json.dumps()
:
json.dumps(obj, default=lambda x: x.__dict__)
Объяснение:
Формируем документы (2.7, 3.6):
''default(obj)'' is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.
(Работает на Python 2.7 и Python 3.x)
Примечание: в этом случае вам нужны переменные instance
а не переменные class
, как пытается сделать пример в вопросе. (Я предполагаю, что спрашивающий имел ввиду class instance
как объект класса)
Я узнал об этом первым из ответа @phihag здесь. Нашел, что это самый простой и чистый способ сделать эту работу.
Ответ 4
Я просто делаю:
data=json.dumps(myobject.__dict__)
Это не полный ответ, и если у вас есть какой-то сложный класс объектов, вы, конечно же, не получите все. Однако я использую это для некоторых моих простых объектов.
Тот, с которым он действительно работает, - это класс "options", который вы получаете из модуля OptionParser.
Здесь он вместе с запросом JSON.
def executeJson(self, url, options):
data=json.dumps(options.__dict__)
if options.verbose:
print data
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
return requests.post(url, data, headers=headers)
Ответ 5
Используя jsonpickle
import jsonpickle
object = YourClass()
json_object = jsonpickle.encode(object)
Ответ 6
JSON не предназначен для сериализации произвольных объектов Python. Это полезно для сериализации объектов dict
, но модуль pickle
действительно то, что вы должны использовать в целом. Выход из pickle
на самом деле не является удобочитаемым для человека, но он должен быть полностью разборчив. Если вы настаиваете на использовании JSON, вы можете проверить модуль jsonpickle
, представляющий интересный гибридный подход.
https://github.com/jsonpickle/jsonpickle
Ответ 7
Вот две простые функции для сериализации любых не-сложных классов, ничего необычного, как объяснялось ранее.
Я использую это для содержимого типа конфигурации, потому что я могу добавлять новые члены в классы без корректировки кода.
import json
class SimpleClass:
def __init__(self, a=None, b=None, c=None):
self.a = a
self.b = b
self.c = c
def serialize_json(instance=None, path=None):
dt = {}
dt.update(vars(instance))
with open(path, "w") as file:
json.dump(dt, file)
def deserialize_json(cls=None, path=None):
def read_json(_path):
with open(_path, "r") as file:
return json.load(file)
data = read_json(path)
instance = object.__new__(cls)
for key, value in data.items():
setattr(instance, key, value)
return instance
# Usage: Create class and serialize under Windows file system.
write_settings = SimpleClass(a=1, b=2, c=3)
serialize_json(write_settings, r"c:\temp\test.json")
# Read back and rehydrate.
read_settings = deserialize_json(SimpleClass, r"c:\temp\test.json")
# results are the same.
print(vars(write_settings))
print(vars(read_settings))
# output:
# {'c': 3, 'b': 2, 'a': 1}
# {'c': 3, 'b': 2, 'a': 1}
Ответ 8
Я верю вместо наследования, как это было предложено в принятом ответе, лучше использовать полиморфизм. В противном случае у вас должен быть большой оператор if else для настройки кодировки каждого объекта. Это означает, что для JSON создается общий кодер по умолчанию:
def jsonDefEncoder(obj):
if hasattr(obj, 'jsonEnc'):
return obj.jsonEnc()
else: #some default behavior
return obj.__dict__
а затем функцию jsonEnc()
в каждом классе, который вы хотите сериализовать. например.
class A(object):
def __init__(self,lengthInFeet):
self.lengthInFeet=lengthInFeet
def jsonEnc(self):
return {'lengthInMeters': lengthInFeet * 0.3 } # each foot is 0.3 meter
Затем вы вызываете json.dumps(classInstance,default=jsonDefEncoder)
Ответ 9
Python3.x
Наилучшим подходом, которого я мог достичь с помощью моих знаний, было это.
Обратите внимание, что этот код также обрабатывает set().
Этот подход является общим, просто нуждающимся в расширении класса (во втором примере).
Обратите внимание, что я просто делаю это с файлами, но легко изменить поведение на свой вкус.
Однако это кодек.
Приложив немного больше работы, вы можете построить свой класс другими способами. Я предполагаю, что конструктор по умолчанию его создает, а затем обновляю класс dict.
import json
import collections
class JsonClassSerializable(json.JSONEncoder):
REGISTERED_CLASS = {}
def register(ctype):
JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype
def default(self, obj):
if isinstance(obj, collections.Set):
return dict(_set_object=list(obj))
if isinstance(obj, JsonClassSerializable):
jclass = {}
jclass["name"] = type(obj).__name__
jclass["dict"] = obj.__dict__
return dict(_class_object=jclass)
else:
return json.JSONEncoder.default(self, obj)
def json_to_class(self, dct):
if '_set_object' in dct:
return set(dct['_set_object'])
elif '_class_object' in dct:
cclass = dct['_class_object']
cclass_name = cclass["name"]
if cclass_name not in self.REGISTERED_CLASS:
raise RuntimeError(
"Class {} not registered in JSON Parser"
.format(cclass["name"])
)
instance = self.REGISTERED_CLASS[cclass_name]()
instance.__dict__ = cclass["dict"]
return instance
return dct
def encode_(self, file):
with open(file, 'w') as outfile:
json.dump(
self.__dict__, outfile,
cls=JsonClassSerializable,
indent=4,
sort_keys=True
)
def decode_(self, file):
try:
with open(file, 'r') as infile:
self.__dict__ = json.load(
infile,
object_hook=self.json_to_class
)
except FileNotFoundError:
print("Persistence load failed "
"'{}' do not exists".format(file)
)
class C(JsonClassSerializable):
def __init__(self):
self.mill = "s"
JsonClassSerializable.register(C)
class B(JsonClassSerializable):
def __init__(self):
self.a = 1230
self.c = C()
JsonClassSerializable.register(B)
class A(JsonClassSerializable):
def __init__(self):
self.a = 1
self.b = {1, 2}
self.c = B()
JsonClassSerializable.register(A)
A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
print(b.b)
print(b.c.a)
редактировать
Проведя дополнительные исследования, я нашел способ обобщения без необходимости вызова метода регистра SUPERCLASS с использованием метакласса.
import json
import collections
REGISTERED_CLASS = {}
class MetaSerializable(type):
def __call__(cls, *args, **kwargs):
if cls.__name__ not in REGISTERED_CLASS:
REGISTERED_CLASS[cls.__name__] = cls
return super(MetaSerializable, cls).__call__(*args, **kwargs)
class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):
def default(self, obj):
if isinstance(obj, collections.Set):
return dict(_set_object=list(obj))
if isinstance(obj, JsonClassSerializable):
jclass = {}
jclass["name"] = type(obj).__name__
jclass["dict"] = obj.__dict__
return dict(_class_object=jclass)
else:
return json.JSONEncoder.default(self, obj)
def json_to_class(self, dct):
if '_set_object' in dct:
return set(dct['_set_object'])
elif '_class_object' in dct:
cclass = dct['_class_object']
cclass_name = cclass["name"]
if cclass_name not in REGISTERED_CLASS:
raise RuntimeError(
"Class {} not registered in JSON Parser"
.format(cclass["name"])
)
instance = REGISTERED_CLASS[cclass_name]()
instance.__dict__ = cclass["dict"]
return instance
return dct
def encode_(self, file):
with open(file, 'w') as outfile:
json.dump(
self.__dict__, outfile,
cls=JsonClassSerializable,
indent=4,
sort_keys=True
)
def decode_(self, file):
try:
with open(file, 'r') as infile:
self.__dict__ = json.load(
infile,
object_hook=self.json_to_class
)
except FileNotFoundError:
print("Persistence load failed "
"'{}' do not exists".format(file)
)
class C(JsonClassSerializable):
def __init__(self):
self.mill = "s"
class B(JsonClassSerializable):
def __init__(self):
self.a = 1230
self.c = C()
class A(JsonClassSerializable):
def __init__(self):
self.a = 1
self.b = {1, 2}
self.c = B()
A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
# 1
print(b.b)
# {1, 2}
print(b.c.a)
# 1230
print(b.c.c.mill)
# s
Ответ 10
Есть несколько хороших ответов о том, как начать это делать. Но есть некоторые вещи, о которых следует помнить:
- Что делать, если экземпляр вложен в большую структуру данных?
- Что делать, если также требуется имя класса?
- Что делать, если вы хотите десериализовать экземпляр?
- Что делать, если вы используете
__slots__
вместо __dict__
?
- Что делать, если вы просто не хотите делать это самостоятельно?
json-tricks - это библиотека (которую я сделал и другие участники), которой удалось это сделать довольно долгое время. Например:
class MyTestCls:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
cls_instance = MyTestCls(s='ub', dct={'7': 7})
json = dumps(cls_instance, indent=4)
instance = loads(json)
Вы вернете свой экземпляр. Здесь json выглядит так:
{
"__instance_type__": [
"json_tricks.test_class",
"MyTestCls"
],
"attributes": {
"s": "ub",
"dct": {
"7": 7
}
}
}
Если вам нравится делать собственное решение, вы можете посмотреть на источник json-tricks
, чтобы не забыть некоторые специальные случаи (например, __slots__
).
Он также выполняет другие типы, такие как массивы numpy, datetimes, сложные числа; он также позволяет комментировать.