Сериализация экземпляра класса в JSON


186

Я пытаюсь создать строковое представление 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.'

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



30
Используйте одну строку, s = json.dumps(obj, default=lambda x: x.__dict__)к переменным экземпляра сериализации объекта ( self.value1, self.value2, ...). Это самый простой и прямой путь. Он будет сериализовать вложенные объектные структуры. defaultФункция вызывается , когда какой - либо данный объект непосредственно не сериализации. Вы также можете посмотреть мой ответ ниже. Я нашел популярные ответы излишне сложными, которые, вероятно, были верны довольно давно.
codeman48

1
У вашего метода testclassнет __init__()метода, поэтому все экземпляры будут использовать одни и те же атрибуты класса ( value1и value2), определенные в операторе класса. Вы понимаете разницу между классом и его экземпляром?
Мартино

1
Для этого есть библиотека python github.com/jsonpickle/jsonpickle (комментируя, так как ответ слишком
наилучшие пожелания

Ответы:


238

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


3
Я попробовал это. Конечный результат вызова json.dumps (t .__ dict__) - просто {}.
Ферхан

6
Это потому, что у вашего класса нет .__init__()функции-метода, поэтому у вашего экземпляра класса есть пустой словарь. Другими словами, {}это правильный результат для вашего примера кода.
Steveha

3
Спасибо. Это делает трюк. Я добавил простой init без параметров, и теперь вызов json.dumps (t .__ dict__) возвращает правильные данные в формате: {"value2": "345", "value1": "123"}. Я видел такие посты, как это раньше, я не был уверен, нужен ли мне специальный сериализатор для членов, необходимость init не упоминалась явно, или я пропустил это. Спасибо.
Ферхан

3
Эта работа для одного класса, но не с соответствующими классами предметов
Nwawel A Iroume

2
@NwawelAIroume: правда. Если у вас есть объект, который, например, содержит несколько объектов в списке, ошибка по-прежнемуis not JSON serializable
gies0r

57

Есть один способ, который отлично работает для меня, который вы можете попробовать:

json.dumps()может принимать необязательный параметр default, где вы можете указать настраиваемую функцию сериализатора для неизвестных типов, которая в моем случае выглядит следующим образом

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__

Первые два if предназначены для сериализации даты и времени, а затем есть obj.__dict__возврат для любого другого объекта.

последний звонок выглядит так:

json.dumps(myObj, default=serialize)

Это особенно хорошо, когда вы сериализуете коллекцию и не хотите __dict__явно вызывать каждый объект. Здесь это делается для вас автоматически.

До сих пор работал так хорошо для меня, с нетерпением жду ваших мыслей.


Я получаю NameError: name 'serialize' is not defined. Какие-нибудь советы?
Кайл Делани

Очень хорошо. Только для классов, в которых есть слоты:try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict
фантастика

Удивительно, что на таком популярном языке нет ни одного лайнера для jsoniny объекта. Должно быть, потому что это не статически напечатано.
TheRennen

49

Вы можете указать 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 здесь . Нашел, что это самый простой и чистый способ сделать работу.


6
Это сработало для меня, но из-за членов datetime.date я немного изменил это:default=lambda x: getattr(x, '__dict__', str(x))
Дакота Хокинс

@ Дакота хороший обходной путь; datetime.dateявляется реализацией C, следовательно, она не имеет __dict__атрибута. ИМХО ради единообразия, datetime.dateдолжно быть это ...
codeman48

22

Я просто:

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)

Возможно, вы захотите удалить себя, если вы не используете это внутри класса.
SpiRail

3
Это будет работать нормально, пока объект не состоит из других объектов.
Haroldo_OK


5

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

https://github.com/jsonpickle/jsonpickle


9
Основная проблема, которую я вижу в pickle, заключается в том, что это специфичный для Python формат, а JSON - это независимый от платформы формат. JSON особенно полезен, если вы пишете веб-приложение или серверную часть для какого-либо мобильного приложения. Это было сказано, спасибо за указание на jsonpickle.
Haroldo_OK

@Haroldo_OK Разве jsonpickle все еще не экспортирует в JSON, просто не очень удобочитаемый?
Caelum

4

Вот две простые функции для сериализации любых несложных классов, ничего необычного, как объяснено ранее.

Я использую это для типа конфигурации, потому что я могу добавлять новые классы без корректировок кода.

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}

3

Есть несколько хороших ответов о том, как начать делать это. Но есть некоторые вещи, которые нужно иметь в виду:

  • Что если экземпляр вложен в большую структуру данных?
  • Что делать, если также хотите имя класса?
  • Что если вы хотите десериализовать экземпляр?
  • Что делать, если вы используете __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__).

Это также делает другие типы как массивы числа, datetime, комплексные числа; это также позволяет для комментариев.


3

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

2

Я считаю, что вместо наследования, как предлагается в принятом ответе, лучше использовать полиморфизм. В противном случае вы должны иметь большой оператор 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)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.