Как сделать класс JSON сериализуемым

Как сделать класс Python сериализуемым?

Простой класс:

class FileItem:
    def __init__(self, fname):
        self.fname = fname

Что мне делать, чтобы получить вывод:

>>> import json

>>> my_file = FileItem('/foo/bar')
>>> json.dumps(my_file)
TypeError: Object of type 'FileItem' is not JSON serializable

Без ошибки


person Sergey    schedule 22.09.2010    source источник
comment
К сожалению, все ответы, кажется, отвечают на вопрос, как сериализовать класс? вместо вопроса о действии Как мне сделать класс сериализуемым? Эти ответы предполагают, что вы сами выполняете сериализацию, а не передаете объект какому-либо другому модулю, который его сериализует.   -  person Kyle Delaney    schedule 18.10.2019
comment
Если вы используете Python3.5 +, вы можете использовать jsons. Он преобразует ваш объект (и все его атрибуты рекурсивно) в dict. import jsons см. Ответ ниже - он отлично работает   -  person tswaehn    schedule 02.04.2020
comment
@KyleDelaney Я действительно надеялся на интерфейс / магический метод, который я мог бы реализовать, чтобы он стал доступным для поиска. Я предполагаю, что мне придется реализовать функцию .to_dict() или что-то, что может быть вызвано для объекта, прежде чем он будет передан модулю, который пытается его сериализовать.   -  person Felix B.    schedule 01.09.2020
comment
см. stackoverflow.com/a/63718624/1497139 для начала работы с примесью JSONAble   -  person Wolfgang Fahl    schedule 03.09.2020
comment
@FelixB. Вы можете использовать встроенную функцию vars в сочетании с json.dumps (см. Мой ответ stackoverflow.com/a/64469761/1587520 )   -  person user1587520    schedule 23.11.2020


Ответы (33)


У вас есть представление об ожидаемом результате? Например, подойдет ли это?

>>> f  = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'

В этом случае вы можете просто позвонить json.dumps(f.__dict__).

Если вам нужен более индивидуальный вывод, вам придется создать подкласс JSONEncoder и реализовать ваша собственная настраиваемая сериализация.

Тривиальный пример см. Ниже.

>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
        def default(self, o):
            return o.__dict__    

>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'

Затем вы передаете этот класс в метод json.dumps() как cls kwarg:

json.dumps(cls=MyEncoder)

Если вы также хотите декодировать, вам нужно будет указать пользовательский object_hook в JSONDecoder класс. Например:

>>> def from_json(json_object):
        if 'fname' in json_object:
            return FileItem(json_object['fname'])
>>> f = JSONDecoder(object_hook = from_json).decode('{"fname": "/foo/bar"}')
>>> f
<__main__.FileItem object at 0x9337fac>
>>> 
person Manoj Govindan    schedule 22.09.2010
comment
Использование __dict__ будет работать не во всех случаях. Если атрибуты не были установлены после создания экземпляра объекта, __dict__ может быть заполнен не полностью. В приведенном выше примере все в порядке, но если у вас есть атрибуты класса, которые вы также хотите закодировать, они не будут перечислены в __dict__, если они не были изменены в вызове класса '__init__ или каким-либо другим способом после того, как объект был создан. - person Kris Hardy; 29.12.2011
comment
+1, но функция from_json(), используемая как объект-перехватчик, должна иметь оператор else: return json_object, чтобы она могла работать и с общими объектами. - person jogojapan; 19.03.2013
comment
@KrisHardy __dict__ также не работает, если вы используете __slots__ в новом классе стилей. - person badp; 13.12.2013
comment
Вы можете использовать настраиваемый JSONEncoder, как указано выше, для создания настраиваемого протокола, например для проверки существования метода __json_serializable__ и его вызова для получения сериализуемого представления объекта в формате JSON. Это будет соответствовать другим шаблонам Python, таким как __getitem__, __str__, __eq__ и __len__. - person jpmc26; 15.07.2015
comment
я новичок в Python ... можем ли мы использовать эти библиотеки (json или simplejson) для сериализации / десериализации объектов, которые широко используются разработчиками Python, говорят, что pandas dataframe, series и т. д.? - person Mahesha999; 20.09.2016
comment
__dict__ также не будет работать рекурсивно, например, если атрибут вашего объекта является другим объектом. - person Neel; 10.04.2018
comment
JSONEncoder вызывает метод подкласса default, только если данные нестандартные. - person yoonghm; 18.09.2018
comment
@ Mahesha999 используйте метод df.to_json () для Dataframe, Series и т. Д. - person user2561747; 07.12.2018

Вот простое решение для простой функции:

.toJSON() Метод

Вместо сериализуемого класса JSON реализуйте метод сериализатора:

import json

class Object:
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)

Итак, вы просто вызываете его для сериализации:

me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"

print(me.toJSON())

выведет:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}
person Onur Yıldırım    schedule 21.03.2013
comment
Очень ограничен. Если у вас есть dict {foo: bar, baz: bat}, он легко будет сериализован в JSON. Если вместо этого у вас есть {foo: bar, baz: MyObject ()}, вы не сможете. В идеале вложенные объекты сериализуются в JSON рекурсивно, а не явно. - person Mark E. Haase; 22.08.2013
comment
Это все равно будет работать. Вам не хватает o.__dict___. Попробуйте свой собственный пример: class MyObject(): def __init__(self): self.prop = 1 j = json.dumps({ "foo": "bar", "baz": MyObject() }, default=lambda o: o.__dict__) - person Onur Yıldırım; 23.08.2013
comment
Это решение обратимо? Т.е. Легко ли восстановить объект из json? - person Jorge Leitao; 26.04.2015
comment
@ J.C.Leitão Нет. У вас может быть два разных класса с одинаковыми полями. Объекты a и b этого класса (вероятно, с одинаковыми свойствами) будут иметь одинаковые a.__dict__ / b.__dict__. - person Martin Thoma; 16.06.2015
comment
Однако вы, вероятно, могли бы добавить поле unjson_class или около того, которое дает подсказку, какой класс изначально имел объект. Затем вы можете использовать самоанализ своего модуля и использовать что-то вроде stackoverflow.com/a/2169191/562769, чтобы вернуть объект . - person Martin Thoma; 16.06.2015
comment
Это не работает с datetime.datetime экземплярами. Выдает следующую ошибку: 'datetime.datetime' object has no attribute '__dict__' - person Bruno Finger; 17.06.2015
comment
Следующее будет работать для всего, что не имеет __dict__: def _try(o): try: return o.__dict__ except: return str(o) и def to_JSON(self): return json.dumps(self, default=lambda o: _try(o), sort_keys=True, indent=0, separators=(',',':')).replace('\n', '') - person Bruno Finger; 17.06.2015
comment
@ J.C.Leitão: да, это обратимо. при загрузке json вы можете добавить каждую пару ключ / значение в свойство объекта dict, см. stackoverflow.com/questions/6578986/ - person FistOfFury; 03.09.2017
comment
Должно быть, мне что-то не хватает, но похоже, что это не работает (например, json.dumps(me) не вызывает метод Object toJSON. - person cglacet; 24.08.2018
comment
Вы можете использовать default=vars вместо default=lambda o: o.__dict__. - person Matthew D. Scholefield; 24.02.2019
comment
@cglacet Это потому, что он не сделал класс сериализуемым, он просто создал метод, который выводит строку JSON. Это неправильный ответ на вопрос, это скорее хитрость для особых случаев. Но правильный выше. Если вам нужно, чтобы YourObject сериализовался как часть / содержимое другого ParentObject, вам необходимо создать кодировщик. - person Javo; 08.09.2020
comment
Вопрос конкретно касается создания json.dumps (). Не о том, как реализовать всю сериализацию JSON самостоятельно. - person Ernest; 23.10.2020

Для более сложных классов вы можете использовать инструмент jsonpickle:

jsonpickle - это библиотека Python для сериализации и десериализации сложных объектов Python в JSON и обратно.

Стандартные библиотеки Python для кодирования Python в JSON, такие как json из stdlib, simplejson и demjson, могут обрабатывать только примитивы Python, которые имеют прямой эквивалент JSON (например, dicts, списки, строки, int и т. Д.). jsonpickle строится поверх этих библиотек и позволяет сериализовать более сложные структуры данных в JSON. jsonpickle обладает широкими возможностями настройки и расширения, что позволяет пользователю выбирать серверную часть JSON и добавлять дополнительные серверные части.

(ссылка на jsonpickle в PyPi)

person gecco    schedule 23.12.2011
comment
Исходя из C #, это то, чего я ожидал. Простой лайнер и никаких проблем с классами. - person Jerther; 14.12.2015
comment
jsonpickle потрясающий. Он отлично работал для огромного, сложного, беспорядочного объекта с множеством уровней классов. - person wisbucky; 04.03.2016
comment
есть ли пример правильного способа сохранить это в файл? В документации показано только, как кодировать и декодировать объект jsonpickle. Кроме того, это не смогло декодировать dict dicts, содержащих фреймы данных pandas. - person user5359531; 16.08.2016
comment
@ user5359531 вы можете использовать obj = jsonpickle.decode(file.read()) и file.write(jsonpickle.encode(obj)). - person Kilian Batzner; 02.01.2017
comment
Вопрос специально для django: имеет ли использование jsonpickle для сериализации данных сеанса ту же уязвимость, что и pickle? (как описано здесь, https://docs.djangoproject.com/en/1.11/topics/http/sessions/#bundled-serializers)? - person Paul Bormans; 23.06.2017
comment
Меня устраивает!. Это то, что мне было нужно. Я просто хотел напечатать объект сценария поведения. - person matabares; 26.05.2020
comment
Это отличное решение. Вместо того, чтобы создавать сложное расширение декодера JSON для модуля json, я смог сериализовать свой класс одной строкой: s = jsonpickle.encode (self). Восстановление было таким же простым. Это настоящий дух Python. +1 - person NoCake; 18.09.2020

Большинство ответов связано с изменением вызова на json.dumps (), что не всегда возможно или желательно (например, это может произойти внутри компонента фреймворка).

Если вы хотите иметь возможность вызывать json.dumps (obj) как есть, тогда простое решение наследуется от dict:

class FileItem(dict):
    def __init__(self, fname):
        dict.__init__(self, fname=fname)

f = FileItem('tasks.txt')
json.dumps(f)  #No need to change anything here

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

person andyhasit    schedule 03.07.2015
comment
Это действительно может быть хорошим решением :) Я считаю, что в моем случае это так. Преимущества: вы сообщаете форму объекта, превращая его в класс с помощью init, он по своей сути сериализуем и выглядит интерпретируемым как repr. - person PascalVKooten; 22.09.2016
comment
Хотя точка доступа все еще отсутствует :( - person PascalVKooten; 22.09.2016
comment
Ах, похоже, работает! Спасибо, не знаю, почему это не принятый ответ. Я полностью согласен с тем, что изменение dumps - не лучшее решение. Кстати, в большинстве случаев вы, вероятно, захотите иметь dict наследование вместе с делегированием, что означает, что у вас будет какой-то атрибут типа dict внутри вашего класса, вы затем передадите этот атрибут в качестве параметра в качестве инициализации, например, super().__init__(self.elements). - person cglacet; 24.08.2018
comment
В моем случае мне нужно было хранить данные, которые были невидимы для json.dumps (), поэтому я использовал этот метод. Класс DictWithRider принимает произвольный объект, сохраняет его как член и делает его доступным через функцию get_rider_obj (), но не передает его в dict .__ init __ (). Таким образом, части приложения, которые хотят видеть скрытые данные, могут вызывать d.get_rider_obj (), но json.dumps () видит в основном пустой dict. Как упоминалось в @PascalVKooten, вы не можете получить доступ к обычным членам с точечной нотацией, но вы можете получить доступ к функциям. - person gkimsey; 21.07.2020
comment
для простого использования это идеально. точечная нотация может быть легко включена с помощью дополнительных строк, следующих за строкой dict.__init__( как self.fname = fname, а объект может быть десериализован с помощью f = FileItem(**json.loads(serialised_f)) - person paddyg; 21.01.2021
comment
Мне очень нравится эта простота. Вы должны полностью разрабатывать свои классы (без обезьяньих исправлений), но это работает действительно хорошо, даже если члены вашего класса являются списками объектов. - person Cerno; 03.05.2021
comment
6 лет спустя это все еще лучший ответ - person rossco; 09.07.2021
comment
Это именно то, что мне нужно, большое вам спасибо - person Qingyi Wu; 19.07.2021

Мне нравится ответ Онура, но я бы расширил его, включив дополнительный метод toJSON() для сериализации объектов:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
person Jason S    schedule 27.01.2015
comment
Я обнаружил, что это лучший баланс между использованием существующего json.dumps и введением настраиваемой обработки. Спасибо! - person Daniel Buckmaster; 15.04.2015
comment
Мне это действительно очень нравится; но вместо try-catch, вероятно, будет сделано что-то вроде _2 _..., чтобы избежать тихого сбоя (в случае сбоя в toJSON () по какой-либо другой причине, чем его отсутствие) ... сбой, который потенциально может привести к повреждению данных. - person thclark; 22.11.2017
comment
@thclark, как я понимаю, idomatic python просит прощения, а не разрешения, поэтому try-except - правильный подход, но должно быть обнаружено правильное исключение, AttributeError в этом случае. - person Phil; 08.09.2020
comment
@phil стал на несколько лет старше и мудрее, я с тобой согласен. - person thclark; 08.09.2020
comment
Это действительно должно явно ловить AttributeError - person juanpa.arrivillaga; 04.02.2021
comment
А что, если AttributeError поднят внутри obj.toJSON()? - person artm; 16.05.2021

Просто добавьте метод to_json в свой класс следующим образом:

def to_json(self):
  return self.message # or how you want it to be serialized

И добавьте этот код (из этого ответа) где-нибудь в начало всего:

from json import JSONEncoder

def _default(self, obj):
    return getattr(obj.__class__, "to_json", _default.default)(obj)

_default.default = JSONEncoder().default
JSONEncoder.default = _default

Это обезьяна исправляет json-модуль при его импорте, поэтому JSONEncoder.default () автоматически проверяет наличие специального метода to_json () и использует его для кодирования объекта, если он найден.

Как и сказал Онур, но на этот раз вам не нужно обновлять все json.dumps() в вашем проекте.

person Fancy John    schedule 04.08.2016
comment
Большое спасибо! Это единственный ответ, который позволяет мне делать то, что я хочу: иметь возможность сериализовать объект без изменения существующего кода. Остальные методы у меня в основном не работают. Объект определен в сторонней библиотеке, и код сериализации также является сторонним. Менять их будет неудобно. С вашим методом мне нужно сделать только TheObject.to_json = my_serializer. - person Yongwei Wu; 11.10.2017

Другой вариант - обернуть дамп JSON в отдельный класс:

import json

class FileItem:
    def __init__(self, fname):
        self.fname = fname

    def __repr__(self):
        return json.dumps(self.__dict__)

Или, что еще лучше, создать подкласс класса FileItem из класса JsonSerializable:

import json

class JsonSerializable(object):
    def toJson(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.toJson()


class FileItem(JsonSerializable):
    def __init__(self, fname):
        self.fname = fname

Тестирование:

>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'
person Paulo Freitas    schedule 16.06.2012
comment
Привет, мне не очень нравится этот подход с настраиваемым кодировщиком, было бы лучше, если бы вы могли сделать свой класс json серийным. Я пытаюсь, и пытаюсь, и ничего. Есть идеи, как это сделать. Дело в том, что модуль json тестирует ваш класс на соответствие встроенным типам python и даже говорит, что для пользовательских классов создайте свой кодировщик :). Это можно подделать? Итак, я мог бы что-нибудь сделать со своим классом, чтобы он вел себя как простой список для модуля json? Я пробую subclasscheck и instancecheck, но ничего. - person Bojan Radojevic; 15.08.2012
comment
@ADRENALIN Вы можете унаследовать от первичного типа (возможно, dict), если все значения атрибутов класса сериализуемы и вы не против взлома. Вы также можете использовать jsonpickle или json_tricks или что-то в этом роде вместо стандартного (все еще пользовательский кодировщик, но не тот, который вам нужно писать или вызывать). Первый обрабатывает экземпляр, второй сохраняет его как атрибуты, которые вы можете изменить, реализовав __json__encode__ / __json_decode__ (раскрытие: я сделал последний). - person Mark; 20.10.2016
comment
Это не делает объект сериализуемым для класса json. Он предоставляет только метод для получения возвращаемой строки json (тривиально). Таким образом json.dumps(f) потерпит неудачу. Спрашивали не об этом. - person omni; 21.09.2020

Как упоминалось во многих других ответах, вы можете передать функцию json.dumps для преобразования объектов, которые не являются одним из типов, поддерживаемых по умолчанию, в поддерживаемый тип. Удивительно, но ни один из них не упоминает простейший случай - использование встроенной функции vars для преобразования объектов в словарь, содержащий все их атрибуты:

json.dumps(obj, default=vars)

Обратите внимание, что это охватывает только базовые случаи, если вам нужна более конкретная сериализация для определенных типов (например, исключение определенных атрибутов или для объектов, у которых нет атрибута __dict__), вам необходимо использовать настраиваемую функцию или JSONEncoder, как описано в других ответах .

person user1587520    schedule 21.10.2020
comment
неясно, что вы имеете в виду под default=vars, означает ли это, что vars является сериализатором по умолчанию? Если нет: это не решает ситуацию, когда вы не можете повлиять на то, как вызывается json.dumps. Если вы просто передаете объект в библиотеку, и эта библиотека вызывает json.dumps для этого объекта, это не поможет, если вы реализовали vars, если эта библиотека не использует dumps таким образом. В этом смысле он эквивалентен обычному JSONEncoder. - person Felix B.; 24.11.2020
comment
Вы правы, это не что иное, как простой выбор пользовательского сериализатора и не решает описанный вами случай. Если я вижу это правильно, нет решения в случае, если бы вы не контролировали, как вызывается json.dumps. - person user1587520; 25.11.2020
comment
Разрешил мой случай. Просто молодец! - person Rub; 02.12.2020
comment
Для некоторых объектов этот подход вызовет vars() argument must have __dict__ attribute - person JustAMartin; 24.02.2021

На днях я столкнулся с этой проблемой и реализовал более общую версию кодировщика для объектов Python, который может обрабатывать вложенные объекты и унаследованные поля:

import json
import inspect

class ObjectEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, "to_json"):
            return self.default(obj.to_json())
        elif hasattr(obj, "__dict__"):
            d = dict(
                (key, value)
                for key, value in inspect.getmembers(obj)
                if not key.startswith("__")
                and not inspect.isabstract(value)
                and not inspect.isbuiltin(value)
                and not inspect.isfunction(value)
                and not inspect.isgenerator(value)
                and not inspect.isgeneratorfunction(value)
                and not inspect.ismethod(value)
                and not inspect.ismethoddescriptor(value)
                and not inspect.isroutine(value)
            )
            return self.default(d)
        return obj

Пример:

class C(object):
    c = "NO"
    def to_json(self):
        return {"c": "YES"}

class B(object):
    b = "B"
    i = "I"
    def __init__(self, y):
        self.y = y
        
    def f(self):
        print "f"

class A(B):
    a = "A"
    def __init__(self):
        self.b = [{"ab": B("y")}]
        self.c = C()

print json.dumps(A(), cls=ObjectEncoder, indent=2, sort_keys=True)

Результат:

{
  "a": "A", 
  "b": [
    {
      "ab": {
        "b": "B", 
        "i": "I", 
        "y": "y"
      }
    }
  ], 
  "c": {
    "c": "YES"
  }, 
  "i": "I"
}
person tobigue    schedule 18.02.2016
comment
Хотя это немного устарело ... Я столкнулся с некоторой ошибкой циклического импорта. Поэтому вместо return obj в последней строке я сделал это return super(ObjectEncoder, self).default(obj). Ссылка ЗДЕСЬ - person SomeTypeFoo; 11.04.2017

Если вы используете Python3.5 +, вы можете использовать jsons. (PyPi: https://pypi.org/project/jsons/) Он преобразует ваш объект (и все его атрибуты рекурсивно) в dict.

import jsons

a_dict = jsons.dump(your_object)

Или, если вам нужна строка:

a_str = jsons.dumps(your_object)

Или, если ваш класс реализовал jsons.JsonSerializable:

a_dict = your_object.json
person R H    schedule 19.12.2018
comment
Если вы можете использовать Python 3.7+, я обнаружил, что наиболее чистое решение для преобразования классов Python в dicts и строки JSON (и наоборот) - это смешать библиотеку jsons с классы данных. Пока все хорошо для меня! - person Ruluk; 26.02.2019
comment
Это внешняя библиотека, не встроенная в стандартную установку Python. - person Noumenon; 10.07.2019
comment
только для класса с атрибутом slots - person yehudahs; 03.12.2019
comment
Вы можете, но вам не нужно использовать слоты. Только при сбросе по сигнатуре определенного класса вам понадобятся слоты. В следующей версии 1.1.0 этого тоже не будет. - person R H; 04.12.2019

import simplejson

class User(object):
    def __init__(self, name, mail):
        self.name = name
        self.mail = mail

    def _asdict(self):
        return self.__dict__

print(simplejson.dumps(User('alice', '[email protected]')))

при использовании стандартного json необходимо определить функцию default

import json
def default(o):
    return o._asdict()

print(json.dumps(User('alice', '[email protected]'), default=default))
person tryer3000    schedule 17.06.2015
comment
Я упростил это, удалив функцию _asdict с лямбда json.dumps(User('alice', '[email protected]'), default=lambda x: x.__dict__) - person JustEngland; 29.11.2018

json ограничен с точки зрения объектов, которые он может напечатать, а jsonpickle (вам может понадобиться pip install jsonpickle) ограничен с точки зрения того, что он не может отступать от текста. Если вы хотите проверить содержимое объекта, класс которого вы не можете изменить, я все равно не смог бы найти более простой способ, чем:

 import json
 import jsonpickle
 ...
 print  json.dumps(json.loads(jsonpickle.encode(object)), indent=2)

Примечание: они по-прежнему не могут распечатать методы объекта.

person ribamar    schedule 04.04.2016

Этот класс может помочь, он преобразует объект в стандартный json.

import json


class Serializer(object):
    @staticmethod
    def serialize(object):
        return json.dumps(object, default=lambda o: o.__dict__.values()[0])

использование:

Serializer.serialize(my_object)

работает в python2.7 и python3.

person Lost Koder    schedule 09.10.2016
comment
Мне больше всего понравился этот метод. Я столкнулся с проблемами при попытке сериализовать более сложные объекты, члены / методы которых не сериализуемы. Вот моя реализация, которая работает с большим количеством объектов: `` class Serializer (object): @staticmethod def serialize (obj): def check (o): for k, v in o .__ dict __. Items (): try: _ = json .dumps (v) o .__ dict __ [k] = v, кроме TypeError: o .__ dict __ [k] = str (v) return o return json.dumps (check (obj) .__ dict__, indent = 2) `` ` - person Will Charlton; 11.11.2017

Вот мои 3 цента ...
Это демонстрирует явную сериализацию json для древовидного объекта python.
Примечание. Если вам действительно нужен такой код, вы можете использовать скрученный класс FilePath.

import json, sys, os

class File:
    def __init__(self, path):
        self.path = path

    def isdir(self):
        return os.path.isdir(self.path)

    def isfile(self):
        return os.path.isfile(self.path)

    def children(self):        
        return [File(os.path.join(self.path, f)) 
                for f in os.listdir(self.path)]

    def getsize(self):        
        return os.path.getsize(self.path)

    def getModificationTime(self):
        return os.path.getmtime(self.path)

def _default(o):
    d = {}
    d['path'] = o.path
    d['isFile'] = o.isfile()
    d['isDir'] = o.isdir()
    d['mtime'] = int(o.getModificationTime())
    d['size'] = o.getsize() if o.isfile() else 0
    if o.isdir(): d['children'] = o.children()
    return d

folder = os.path.abspath('.')
json.dump(File(folder), sys.stdout, default=_default)
person Dan Brough    schedule 10.07.2013

jaraco дал довольно точный ответ. Мне нужно было исправить некоторые мелочи, но это работает:

Код

# Your custom class
class MyCustom(object):
    def __json__(self):
        return {
            'a': self.a,
            'b': self.b,
            '__python__': 'mymodule.submodule:MyCustom.from_json',
        }

    to_json = __json__  # supported by simplejson

    @classmethod
    def from_json(cls, json):
        obj = cls()
        obj.a = json['a']
        obj.b = json['b']
        return obj

# Dumping and loading
import simplejson

obj = MyCustom()
obj.a = 3
obj.b = 4

json = simplejson.dumps(obj, for_json=True)

# Two-step loading
obj2_dict = simplejson.loads(json)
obj2 = MyCustom.from_json(obj2_dict)

# Make sure we have the correct thing
assert isinstance(obj2, MyCustom)
assert obj2.__dict__ == obj.__dict__

Обратите внимание, что нам нужно два шага для загрузки. На данный момент свойство __python__ не используется.

Насколько это распространено?

Используя метод AlJohri, проверяю популярность подходов:

Сериализация (Python -> JSON):

Десериализация (JSON -> Python):

  • from_json: 226 101 на 27 июня 2018 г.
person Martin Thoma    schedule 27.06.2018

Это хорошо сработало для меня:

class JsonSerializable(object):

    def serialize(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.serialize()

    @staticmethod
    def dumper(obj):
        if "serialize" in dir(obj):
            return obj.serialize()

        return obj.__dict__

а потом

class FileItem(JsonSerializable):
    ...

а также

log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))
person jmhostalet    schedule 18.01.2019

Если вы не против установить для него пакет, вы можете использовать json-tricks:

pip install json-tricks

После этого вам просто нужно импортировать dump(s) из json_tricks вместо json, и обычно это сработает:

from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)

что даст

{
        "__instance_type__": [
                "module_name.test_class",
                "MyTestCls"
        ],
        "attributes": {
                "attr": "val",
                "dct_attr": {
                        "hello": 42
                }
        }
}

Вот и все!


В целом это будет отлично работать. Есть некоторые исключения, например если в __new__ происходят особые вещи, или происходит больше магии метакласса.

Очевидно загрузка тоже работает (а то в чем смысл):

from json_tricks import loads
json_str = loads(json_str)

Это предполагает, что module_name.test_class.MyTestCls может быть импортирован и не был изменен несовместимыми способами. Вы получите обратно экземпляр, а не какой-то словарь или что-то в этом роде, и он должен быть идентичной копией тому, который вы сбросили.

Если вы хотите настроить способ (де) сериализации чего-либо, вы можете добавить в свой класс специальные методы, например:

class CustomEncodeCls:
        def __init__(self):
                self.relevant = 42
                self.irrelevant = 37

        def __json_encode__(self):
                # should return primitive, serializable types like dict, list, int, string, float...
                return {'relevant': self.relevant}

        def __json_decode__(self, **attrs):
                # should initialize all properties; note that __init__ is not called implicitly
                self.relevant = attrs['relevant']
                self.irrelevant = 12

который, например, сериализует только часть параметров атрибутов.

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

Отказ от ответственности: я создал json_tricks, потому что у меня была та же проблема, что и у вас.

person Mark    schedule 10.11.2016
comment
Я только что протестировал json_tricks, и он работал красиво (в 2019 году). - person pauljohn32; 06.11.2019

Комментарий Кайла Делани верен, поэтому я попытался использовать ответ https://stackoverflow.com/a/15538391/1497139, а также улучшенная версия https://stackoverflow.com/a/10254820/1497139

для создания миксина JSONAble.

Итак, чтобы сделать класс JSON сериализуемым, используйте JSONAble в качестве суперкласса и либо вызовите:

 instance.toJSON()

or

 instance.asJSON()

для двух предложенных методов. Вы также можете расширить класс JSONAble другими предлагаемыми здесь подходами.

Пример теста для модульного теста с семейством и личностью дает следующие результаты:

toJSOn ():

{
    "members": {
        "Flintstone,Fred": {
            "firstName": "Fred",
            "lastName": "Flintstone"
        },
        "Flintstone,Wilma": {
            "firstName": "Wilma",
            "lastName": "Flintstone"
        }
    },
    "name": "The Flintstones"
}

asJSOn ():

{'name': 'The Flintstones', 'members': {'Flintstone,Fred': {'firstName': 'Fred', 'lastName': 'Flintstone'}, 'Flintstone,Wilma': {'firstName': 'Wilma', 'lastName': 'Flintstone'}}}

Модульный тест с образцом семьи и человека

def testJsonAble(self):
        family=Family("The Flintstones")
        family.add(Person("Fred","Flintstone")) 
        family.add(Person("Wilma","Flintstone"))
        json1=family.toJSON()
        json2=family.asJSON()
        print(json1)
        print(json2)

class Family(JSONAble):
    def __init__(self,name):
        self.name=name
        self.members={}
    
    def add(self,person):
        self.members[person.lastName+","+person.firstName]=person

class Person(JSONAble):
    def __init__(self,firstName,lastName):
        self.firstName=firstName;
        self.lastName=lastName;

jsonable.py, определяющий миксин JSONAble

 '''
Created on 2020-09-03

@author: wf
'''
import json

class JSONAble(object):
    '''
    mixin to allow classes to be JSON serializable see
    https://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable
    '''

    def __init__(self):
        '''
        Constructor
        '''
    
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)
        
    def getValue(self,v):
        if (hasattr(v, "asJSON")):
            return v.asJSON()
        elif type(v) is dict:
            return self.reprDict(v)
        elif type(v) is list:
            vlist=[]
            for vitem in v:
                vlist.append(self.getValue(vitem))
            return vlist
        else:   
            return v
    
    def reprDict(self,srcDict):
        '''
        get my dict elements
        '''
        d = dict()
        for a, v in srcDict.items():
            d[a]=self.getValue(v)
        return d
    
    def asJSON(self):
        '''
        recursively return my dict elements
        '''
        return self.reprDict(self.__dict__)   

Эти подходы теперь интегрированы в проект https://github.com/WolfgangFahl/pyLoDStorage. который доступен по адресу https://pypi.org/project/pylodstorage/

person Wolfgang Fahl    schedule 03.09.2020

jsonweb кажется мне лучшим решением. См. http://www.jsonweb.info/en/latest/.

from jsonweb.encode import to_object, dumper

@to_object()
class DataModel(object):
  def __init__(self, id, value):
   self.id = id
   self.value = value

>>> data = DataModel(5, "foo")
>>> dumper(data)
'{"__type__": "DataModel", "id": 5, "value": "foo"}'
person matthewlent    schedule 07.10.2014
comment
Хорошо ли это работает для вложенных объектов? Включая декодирование и кодирование - person Simone Zandara; 22.12.2015

Основываясь на Quinten Cabo "> ответ:

def sterilize(obj):
    """Make an object more ameniable to dumping as json
    """
    if type(obj) in (str, float, int, bool, type(None)):
        return obj
    elif isinstance(obj, dict):
        return {k: sterilize(v) for k, v in obj.items()}
    list_ret = []
    dict_ret = {}
    for a in dir(obj):
        if a == '__iter__' and callable(obj.__iter__):
            list_ret.extend([sterilize(v) for v in obj])
        elif a == '__dict__':
            dict_ret.update({k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']})
        elif a not in ['__doc__', '__module__']:
            aval = getattr(obj, a)
            if type(aval) in (str, float, int, bool, type(None)):
                dict_ret[a] = aval
            elif a != '__class__' and a != '__objclass__' and isinstance(aval, type):
                dict_ret[a] = sterilize(aval)
    if len(list_ret) == 0:
        if len(dict_ret) == 0:
            return repr(obj)
        return dict_ret
    else:
        if len(dict_ret) == 0:
            return list_ret
    return (list_ret, dict_ret)

Различия заключаются

  1. Работает для любой итерации, а не только для list и tuple (работает для массивов NumPy и т. Д.)
  2. Работает для динамических типов (содержащих __dict__).
  3. Включает собственные типы float и None, поэтому они не преобразуются в строку.
  4. Классы с __dict__ и членами будут в основном работать (если имена __dict__ и членов совпадают, вы получите только один - скорее всего, член)
  5. Классы, которые являются списками и имеют члены, будут выглядеть как кортеж из списка и словаря.
  6. Python3 (этот isinstance() вызов может быть единственным, что нужно изменить)
person mheyman    schedule 02.05.2020

Мне больше всего понравился метод Лоста Кодера. Я столкнулся с проблемами при попытке сериализовать более сложные объекты, члены / методы которых не сериализуемы. Вот моя реализация, которая работает с большим количеством объектов:

class Serializer(object):
    @staticmethod
    def serialize(obj):
        def check(o):
            for k, v in o.__dict__.items():
                try:
                    _ = json.dumps(v)
                    o.__dict__[k] = v
                except TypeError:
                    o.__dict__[k] = str(v)
            return o
        return json.dumps(check(obj).__dict__, indent=2)
person Will Charlton    schedule 11.11.2017

Я столкнулся с этой проблемой, когда пытался сохранить модель Peewee в PostgreSQL JSONField.

После некоторой борьбы вот общее решение.

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

Предположим, у вас есть модель, которая содержит некоторые поля, не сериализуемые в JSON, а модель, содержащая поле JSON, изначально выглядит следующим образом:

class SomeClass(Model):
    json_field = JSONField()

Просто определите пользовательский JSONEncoder следующим образом:

class CustomJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, SomeTypeUnsupportedByJsonDumps):
            return < whatever value you want >
        return json.JSONEncoder.default(self, obj)

    @staticmethod
    def json_dumper(obj):
        return json.dumps(obj, cls=CustomJsonEncoder)

А затем просто используйте его в своем JSONField, как показано ниже:

class SomeClass(Model):
    json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)

Ключ - это метод default(self, obj), описанный выше. Для каждой ... is not JSON serializable жалобы, которую вы получаете от Python, просто добавьте код для обработки несериализуемого в JSON типа (например, Enum или datetime)

Например, вот как я поддерживаю класс, унаследованный от Enum:

class TransactionType(Enum):
   CURRENT = 1
   STACKED = 2

   def default(self, obj):
       if isinstance(obj, TransactionType):
           return obj.value
       return json.JSONEncoder.default(self, obj)

Наконец, с кодом, реализованным, как указано выше, вы можете просто преобразовать любые модели Peewee в объект с сериализацией JSON, как показано ниже:

peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)

Хотя приведенный выше код был (в некоторой степени) специфичным для Peewee, но я думаю:

  1. Это применимо к другим ORM (Django и т. Д.) В целом.
  2. Кроме того, если вы понимаете, как работает json.dumps, это решение также работает с Python (без ORM) в целом.

Любые вопросы пишите в комментариях. Спасибо!

person sivabudh    schedule 30.07.2018

Сначала нам нужно сделать наш объект JSON-совместимым, чтобы мы могли выгрузить его с помощью стандартного модуля JSON. Я сделал это так:

def serialize(o):
    if isinstance(o, dict):
        return {k:serialize(v) for k,v in o.items()}
    if isinstance(o, list):
        return [serialize(e) for e in o]
    if isinstance(o, bytes):
        return o.decode("utf-8")
    return o
person Adi Degani    schedule 27.02.2020

Эта функция использует рекурсию для итерации по каждой части словаря, а затем вызывает методы repr () классов, которые не являются встроенными типами.

def sterilize(obj):
    object_type = type(obj)
    if isinstance(obj, dict):
        return {k: sterilize(v) for k, v in obj.items()}
    elif object_type in (list, tuple):
        return [sterilize(v) for v in obj]
    elif object_type in (str, int, bool, float):
        return obj
    else:
        return obj.__repr__()
person Quinten Cabo    schedule 30.03.2020

Я придумал собственное решение. Используйте этот метод для передачи любого документа (dict, list, ObjectId и т. Д.) Для сериализации.

def getSerializable(doc):
    # check if it's a list
    if isinstance(doc, list):
        for i, val in enumerate(doc):
            doc[i] = getSerializable(doc[i])
        return doc

    # check if it's a dict
    if isinstance(doc, dict):
        for key in doc.keys():
            doc[key] = getSerializable(doc[key])
        return doc

    # Process ObjectId
    if isinstance(doc, ObjectId):
        doc = str(doc)
        return doc

    # Use any other custom serializting stuff here...

    # For the rest of stuff
    return doc
person Dewsworld    schedule 21.05.2015

Если вы можете установить пакет, я бы рекомендовал попробовать dill, который отлично работал для мой проект. Хорошая особенность этого пакета заключается в том, что он имеет тот же интерфейс, что и pickle, поэтому, если вы уже использовали pickle в своем проекте, вы можете просто заменить его на dill и посмотреть, запускается ли сценарий, без изменения кода. Так что это очень дешевое решение!

(Полный запрет на раскрытие информации: я никоим образом не связан и никогда не участвовал в проекте укропа.)

Установите пакет:

pip install dill

Затем отредактируйте свой код, чтобы импортировать dill вместо pickle:

# import pickle
import dill as pickle

Запустите свой сценарий и посмотрите, работает ли он. (Если это так, вы можете очистить свой код, чтобы больше не скрывать имя модуля pickle!)

Некоторые особенности типов данных, которые dill могут и не могут сериализоваться, на странице проекта:

dill можно мариновать следующие стандартные виды:

none, type, bool, int, long, float, complex, str, unicode, tuple, list, dict, file, buffer, builtin, старые и новые классы стилей, экземпляры старых и новых классов стилей, set, frozenset, массив , функции, исключения

dill также можно мариновать более «экзотические» стандартные сорта:

функции с выходами, вложенные функции, лямбды, ячейка, метод, unboundmethod, модуль, код, метод-оболочка, dictproxy, дескриптор метода, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, slice, notimplemented, многоточие, выход

dill пока не может засолить эти стандартные типы:

кадр, генератор, трассировка

person thedavidmo    schedule 18.12.2018

Я не вижу здесь упоминания о серийном управлении версиями или обратной совместимости, поэтому я опубликую свое решение, которое я использовал некоторое время. Мне, вероятно, есть чему поучиться, в частности, Java и Javascript, вероятно, более зрелые, чем я, но здесь идет

https://gist.github.com/andy-d/b7878d0044a4242c0498ed6d67fd50feed6d67fd50

person Fletch F Fletch    schedule 27.08.2019

Чтобы добавить еще один вариант: вы можете использовать пакет attrs и метод asdict.

class ObjectEncoder(JSONEncoder):
    def default(self, o):
        return attr.asdict(o)

json.dumps(objects, cls=ObjectEncoder)

и конвертировать обратно

def from_json(o):
    if '_obj_name' in o:
        type_ = o['_obj_name']
        del o['_obj_name']
        return globals()[type_](**o)
    else:
        return o

data = JSONDecoder(object_hook=from_json).decode(data)

класс выглядит так

@attr.s
class Foo(object):
    x = attr.ib()
    _obj_name = attr.ib(init=False, default='Foo')
person machinekoder    schedule 15.10.2019

В дополнение к ответу Онура вы, возможно, захотите иметь дело с типом datetime, как показано ниже.
(для обработки : объект 'datetime.datetime' не имеет исключения атрибута 'dict'.)

def datetime_option(value):
    if isinstance(value, datetime.date):
        return value.timestamp()
    else:
        return value.__dict__

Использование:

def toJSON(self):
    return json.dumps(self, default=datetime_option, sort_keys=True, indent=4)
person Mark Choi    schedule 03.02.2020

Это небольшая библиотека, которая сериализует объект со всеми его дочерними элементами в JSON, а также анализирует его обратно:

https://github.com/tobiasholler/PyJSONSerialization/

person Tobi    schedule 17.07.2014

Есть много подходов к этой проблеме. ObjDict (pip install objdict) - другое. Особое внимание уделяется предоставлению объектов, подобных javascript, которые также могут действовать как словари для лучшей обработки данных, загружаемых из JSON, но есть и другие функции, которые также могут быть полезны. Это дает еще одно альтернативное решение исходной проблемы.

person innov8    schedule 02.10.2016

Я решил использовать декораторы для решения проблемы сериализации объекта datetime. Вот мой код:

#myjson.py
#Author: jmooremcc 7/16/2017

import json
from datetime import datetime, date, time, timedelta
"""
This module uses decorators to serialize date objects using json
The filename is myjson.py
In another module you simply add the following import statement:
    from myjson import json

json.dumps and json.dump will then correctly serialize datetime and date 
objects
"""

def json_serial(obj):
    """JSON serializer for objects not serializable by default json code"""

    if isinstance(obj, (datetime, date)):
        serial = str(obj)
        return serial
    raise TypeError ("Type %s not serializable" % type(obj))


def FixDumps(fn):
    def hook(obj):
        return fn(obj, default=json_serial)

    return hook

def FixDump(fn):
    def hook(obj, fp):
        return fn(obj,fp, default=json_serial)

    return hook


json.dumps=FixDumps(json.dumps)
json.dump=FixDump(json.dump)


if __name__=="__main__":
    today=datetime.now()
    data={'atime':today, 'greet':'Hello'}
    str=json.dumps(data)
    print str

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

person John Moore    schedule 16.07.2017

person    schedule
comment
Из doc: параметр default(obj) - это функция, которая должна возвращать сериализуемую версию obj или поднять TypeError. Значение по умолчанию default просто вызывает TypeError. - person luckydonald; 28.06.2016