Сериализация экземпляра класса в JSON
Я пытаюсь создать строковое представление JSON экземпляра класса и возникли трудности. Допустим, класс построен следующим образом:
class testclass:
value1 = "a"
value2 = "b"
вызова в формате JSON.дампы делаются так:
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'x80x03c__main__ntestclassnqx00)x81qx01}qx02b.'
что я делаю не так?
10 ответов:
основная проблема заключается в том, что кодер JSON
json.dumps()знает только как сериализовать ограниченный набор типов объектов по умолчанию все встроенные типы. Список здесь: https://docs.python.org/3.3/library/json.html#encoders-and-decodersодним хорошим решением было бы сделать ваш класс наследуется от
JSONEncoderа затем реализоватьJSONEncoder.default()функция, и сделать эту функцию излучать правильный JSON для вашего класса.простым решением было бы позвонить
json.dumps()на.__dict__член этого экземпляра. Это стандартный Pythondictи если ваш класс прост, он будет сериализуемым 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__
Примечание: я отредактировал этот ответ; в оригинальной версии обсуждался только
.__dict__подхода сериализации.
есть один способ, который отлично работает для меня, что вы можете попробовать:
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__конкретно для каждого объекта. Здесь это делается для вас автоматически.до сих пор работал так хорошо для меня, с нетерпением ждем ваших мыслей.
просто я:
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)
вы можете указать
defaultименованный параметр в тег :json.dumps(obj, default=lambda x: x.__dict__)объяснение:
``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 здесь. Оказалось, что это самый простой и чистый способ сделать работу.
С помощью jsonpickle
import jsonpickle object = YourClass() json_object = jsonpickle.encode(object)
JSON на самом деле не предназначен для сериализации произвольных объектов Python. Это отлично подходит для сериализации
dictобъекты, ноpickleмодуль-это действительно то, что вы должны использовать в целом. Выход изpickleна самом деле не читается человеком, но это должно быть просто отлично. Если вы настаиваете на использовании JSON, вы можете проверитьjsonpickleмодуль, который представляет собой интересный гибрид подход.
вот две простые функции для сериализации любых несложных классов, ничего необычного, как объяснялось ранее.
Я использую это для материала типа конфигурации, потому что я могу добавлять новые члены в классы без корректировки кода.
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}
Я считаю, что вместо наследования, как предлагается в принятом ответе, лучше использовать полиморфизм. В противном случае у вас должен быть большой оператор 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)
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)
Edit
С некоторыми более исследований я нашел способ обобщить без необходимости суперкласс Регистрация вызова метода, с помощью метакласс
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
есть несколько хороших ответов о том, как начать делать это. Но есть некоторые вещи, чтобы иметь в виду:
- Что делать, если экземпляр вложен в большую структуру данных?
- Что делать, если также хотите имя класса?
- Что делать, если вы хотите, чтобы десериализовать экземпляр?
- Что делать, если вы используете
__slots__вместо__dict__?- Что делать, если вы просто не хотите это делать ты сам?
в JSON-приколы это библиотека (которую я сделал и другие внесли свой вклад), которая была в состоянии сделать это в течение довольно долгого времени. Например:
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, даты, комплексные числа; он также позволяет комментировать.
Comments