Сериализация экземпляра класса в 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.'


что я делаю не так?

734   10  

10 ответов:

основная проблема заключается в том, что кодер 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__

Примечание: я отредактировал этот ответ; в оригинальной версии обсуждался только .__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__)

объяснение:

формы документов ( 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 здесь. Оказалось, что это самый простой и чистый способ сделать работу.

С помощью jsonpickle

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)

JSON на самом деле не предназначен для сериализации произвольных объектов Python. Это отлично подходит для сериализации dict объекты, но pickle модуль-это действительно то, что вы должны использовать в целом. Выход из pickle на самом деле не читается человеком, но это должно быть просто отлично. Если вы настаиваете на использовании JSON, вы можете проверить jsonpickle модуль, который представляет собой интересный гибрид подход.

https://github.com/jsonpickle/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

    Ничего не найдено.