Сериализация экземпляра класса в 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, сложные числа; он также позволяет комментировать.