Як зробити клас JSON серійним


833

Як зробити клас 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

Без помилки


30
Прикро, що всі відповіді, здається, відповідають на питання "Як я серіалізую клас?" а не питання дії "Як зробити клас серіалізаційним?" Ці відповіді передбачають, що ви робите серіалізацію самостійно, а не передаєте об'єкт до якогось іншого модуля, який його серіалізує.
Кайл Делані

Якщо ви використовуєте Python3.5 +, ви можете використовувати jsons. Він перетворить ваш об’єкт (і всі його атрибути рекурсивно ) в дикт. import jsonsдивіться відповідь нижче - це працює чудово
tswaehn

Відповіді:


551

Чи маєте ви уявлення про очікуваний вихід? Бо, наприклад, це зробить?

>>> 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()методу як clskwarg:

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>
>>> 

44
Використання __dict__працюватиме не у всіх випадках. Якщо атрибути не були встановлені після інстанціїзації об'єкта, він __dict__може бути не повністю заповнений. У наведеному вище прикладі ви все в порядку, але якщо у вас є атрибути класу, які ви також хочете кодувати, вони не будуть вказані в списку, __dict__якщо вони не були змінені у __init__виклику класу або іншим способом після інстанцізації об'єкта.
Кріс Харді

8
+1, але from_json()функція, що використовується як об'єкт-гачок, повинна мати else: return json_objectоператор, тому вона може також працювати з загальними об'єктами.
jogojapan

8
@KrisHardy __dict__також не працює, якщо ви використовуєте __slots__новий клас стилів.
badp

7
Ви можете використовувати звичай, JSONEncoderяк описано вище, для створення користувальницького протоколу, наприклад, перевірки __json_serializable__на предмет існування методу та виклику його для отримання JSON серіалізаційного представлення об'єкта. Це було б відповідно до іншими шаблонами Python, як __getitem__, __str__, __eq__, і __len__.
jpmc26

5
__dict__також не працюватиме рекурсивно, наприклад, якщо атрибут вашого об'єкта є іншим об'єктом.
Ніл

634

Ось просте рішення для простої функції:

.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"
}

82
Дуже обмежений. Якщо у вас є dict {"foo": "bar", "baz": "bat"}, це легко серіалізується до JSON. Якщо замість цього у вас є {"foo": "bar", "baz": MyObject ()}, ви не можете. Ідеальною ситуацією було б те, щоб вкладені об'єкти серіалізувалися в JSON рекурсивно, а не явно.
Марк Е. Хааз

30
Це все одно спрацює. Ви пропали безвісти o.__dict___. Спробуйте власний приклад: class MyObject(): def __init__(self): self.prop = 1 j = json.dumps({ "foo": "bar", "baz": MyObject() }, default=lambda o: o.__dict__)
Onur Yıldırım

14
Чи є це рішення оборотним? Тобто легко реконструювати об’єкт від json?
Хорхе Лейтао

2
@ JCLeitão Ні. У вас могли бути два різних класи з однаковими полями. Об'єкти a і b цього класу (ймовірно, з однаковими властивостями) мали б однаковий a.__dict__/ b.__dict__.
Мартін Тома

7
Це не працює з datetime.datetimeекземплярами. Це кидає таку помилку:'datetime.datetime' object has no attribute '__dict__'
Бруно Фінгер

171

Для більш складних класів ви можете розглянути інструмент jsonpickle :

jsonpickle - бібліотека Python для серіалізації та десеріалізації складних об'єктів Python до та від JSON.

Стандартні бібліотеки Python для кодування Python в JSON, такі як json stdlib, simplejson та demjson, можуть обробляти лише примітиви Python, які мають прямий еквівалент JSON (наприклад, дикти, списки, рядки, ints тощо). jsonpickle ґрунтується на цих бібліотеках і дозволяє більш складні структури даних серіалізувати до JSON. jsonpickle дуже настроюється та розширюється, що дозволяє користувачеві вибирати сервер JSON і додавати додаткові мітки.

(посилання на jsonpickle на PyPi)


31
Походить із C #, це я очікував. Простий лайнер і не возитися з класами.
Джертер

2
jsonpickle - приголомшливий. Він прекрасно працював для величезного, складного, безладного об’єкта з багатьма рівнями класів
wisbucky

чи є приклад правильного способу збереження цього у файлі? Документація лише показує, як кодувати і декодувати jsonpickleоб’єкт. Крім того, це не вдалося розшифрувати набір диктів, що містять фрейми даних панд.
користувач5359531

3
@ user5359531 ви можете використовувати obj = jsonpickle.decode(file.read())і file.write(jsonpickle.encode(obj)).
Кіліан Бацнер

1
Питання спеціально до django: чи використання jsonpickle для серіалізації даних сеансу має таку ж вразливість, як мариновані? (як описано тут docs.djangoproject.com/en/1.11/topics/http/sesions/… )?
Пол Борманс

89

Більшість відповідей стосуються зміни дзвінка на 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

Це працює, якщо ваш клас є лише базовим поданням даних, для складніших речей завжди можна чітко встановити клавіші.


2
Це дійсно може бути приємним рішенням :) Я вірю, що для моєї справи це так. Переваги: ​​ви повідомляєте "форму" об'єкта, роблячи його класом з init, він за своєю суттю серіалізується і виглядає інтерпретованим як repr .
PascalVKooten

1
Хоча "крапка доступу" все ще відсутня :(
PascalVKooten

2
Ах, це, здається, працює! Дякую, не впевнений, чому це не прийнята відповідь. Я повністю погоджуюся, що змінити dumpsце не вдале рішення. До речі, у більшості випадків ви, мабуть, хочете мати dictспадщину разом з делегуванням, а це означає, що у вас буде якийсь dictатрибут типу всередині вашого класу, ви передасте цей атрибут як параметр як ініціалізацію щось подібне super().__init__(self.elements).
cglacet

47

Мені подобається відповідь Онура, але вона розшириться, щоб включити необов'язковий toJSON()метод, щоб об'єкти серіалізували себе:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)

Я вважав, що це найкращий баланс між використанням існуючих json.dumpsта запровадженням користувальницької обробки. Дякую!
Даніель Бакмастер

12
Мені це справді дуже подобається; але замість try-catchцього, мабуть, зробить щось на кшталт if 'toJSON' in obj.__attrs__():... щоб уникнути мовчазного відмови (у разі відмови в toJSON () з якоїсь іншої причини, ніж його немає) ... відмови, який потенційно може призвести до пошкодження даних.
thclark

39

Інший варіант - перетворити демпінг 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"}'

2
Привіт, мені не дуже подобається такий підхід "користувальницького кодера", було б краще, якщо ти можеш зробити свій json класу серійним. Я пробую, і пробую, і намагаюся, і нічого. Чи є ідея, як це зробити. Вся справа в тому, що модуль json перевіряє ваш клас на вбудовані типи python, і навіть каже, що для користувацьких класів зробіть ваш кодер :). Чи можна це підробити? Тож я міг би щось зробити для свого класу, щоб він поводився як простий список до модуля json? Я намагаюся підкласифікацію та інспекцію, але нічого.
Bojan Radojevic

@ADRENALIN Ви можете успадкувати від основного типу (ймовірно, диктату), якщо всі значення атрибутів класу серіалізуються і ви не заперечуєте проти хаків. Ви також можете використовувати jsonpickle або json_tricks або щось інше замість стандартного (все-таки користувацький кодер, але не той, який потрібно писати або дзвонити). Перший підбирає екземпляр, останній зберігає його як набір атрибутів, який ви можете змінити, ввівши __json__encode__/ __json_decode__ввівши: (я розробив останній).
Марк

30

Просто додайте 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()у своєму проекті.


6
Велике спасибі! Це єдина відповідь, яка дозволяє мені робити те, що я хочу: вміти серіалізувати об’єкт без зміни існуючого коду. Інші методи для мене здебільшого не працюють. Об'єкт визначений у сторонній бібліотеці, і код серіалізації є стороннім. Змінювати їх буде незручно. З вашим методом мені потрібно лише зайнятися TheObject.to_json = my_serializer.
Yongwei Wu

24

Я натрапив на цю проблему днями і реалізував більш загальну версію кодера для об’єктів 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"
}

1
Хоча це трохи старенько ... Я зіткнувся з деякою круговою помилкою імпорту. Тож замість return objостаннього рядка я це зробив return super(ObjectEncoder, self).default(obj). Довідка ТУТ
SomeTypeFoo

24

Якщо ви використовуєте Python3.5 +, ви можете використовувати jsons. Він перетворить ваш об’єкт (і всі його атрибути рекурсивно) в дикт.

import jsons

a_dict = jsons.dump(your_object)

Або якщо ви хотіли рядок:

a_str = jsons.dumps(your_object)

Або якщо ваш клас реалізований jsons.JsonSerializable:

a_dict = your_object.json

3
Якщо ви в змозі використовувати Python 3.7+, я виявив, що найчистішим рішенням для перетворення класів python у дикти та JSON-рядки (і viceversa) є змішування jsonsбібліотеки з класами даних . Поки так добре для мене!
Рулук

3
Це зовнішня бібліотека, не вбудована в стандартну установку Python.
Ноумен

лише для класу, який має атрибут слотів
yehudahs

Можна, але не потрібно використовувати слоти . Тільки під час демпінгу відповідно до підпису певного класу вам потрібні слоти . У майбутній версії 1.1.0 це теж не так.
RH

11
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', 'alice@mail.com')))

якщо використовується стандарт json, вам потрібно визначити defaultфункцію

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

print(json.dumps(User('alice', 'alice@mail.com'), default=default))

2
Я спростив це, видаливши функцію _asdict за допомогою лямбда json.dumps(User('alice', 'alice@mail.com'), default=lambda x: x.__dict__)
JustEngland

8

jsonобмежений з точки зору об'єктів, які він може друкувати, і jsonpickle(можливо, вам знадобиться pip install jsonpickle) обмежений тим, що він не може відступати тексту. Якщо ви хочете перевірити вміст об'єкта, клас якого ви не можете змінити, я все-таки не зміг знайти більш прямий спосіб, ніж:

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

Зауважте: що досі вони не можуть надрукувати об'єктні методи.


6

Цей клас може зробити трюк, він перетворює об’єкт у стандартний 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.


Мені цей метод найбільше сподобався. У мене виникли проблеми, коли я намагався серіалізувати складніші об'єкти, члени учасників / методи яких не піддаються серіалізації. Ось моя реалізація, яка працює над іншими об'єктами: `` `Серіалізатор класу (об’єкт): @staticmethod def serialize (obj): def check (o): для 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) `` `
Will Чарлтон

4
import json

class Foo(object):
    def __init__(self):
        self.bar = 'baz'
        self._qux = 'flub'

    def somemethod(self):
        pass

def default(instance):
    return {k: v
            for k, v in vars(instance).items()
            if not str(k).startswith('_')}

json_foo = json.dumps(Foo(), default=default)
assert '{"bar": "baz"}' == json_foo

print(json_foo)

Від doc : Параметр default(obj)- це функція, яка повинна повернути серіалізаційну версію obj або підняти TypeError. За замовчуванням defaultпросто підвищується TypeError.
luckydonald

4

ярако дав досить акуратну відповідь. Мені потрібно було виправити деякі незначні речі, але це працює:

Код

# 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__ майно не використовується.

Наскільки це поширене?

Використовуючи метод АльДжорі , я перевіряю популярність підходів:

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

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


4

Це добре для мене:

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))

3

Якщо ви не проти встановити пакет для нього, ви можете використовувати json-трюки :

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

яка серіалізує лише частину параметрів атрибутів, як приклад.

І як безкоштовний бонус, ви отримуєте (де) серіалізацію нумерових масивів, дату та час, упорядковані карти, а також можливість включати коментарі в json.

Відмова: Я створив json_tricks , тому що у мене була така ж проблема, як у вас.


1
Я щойно тестував json_tricks і він спрацював красиво (у 2019 році).
pauljohn32

2

jsonweb, здається, найкраще рішення для мене. Дивіться http://www.jsonweb.info/uk/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"}'

Чи добре це працює для вкладених об’єктів? Включаючи розшифровку та кодування
Симоне Зандара

1

Ось мої 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)

1

Я зіткнувся з цією проблемою, коли спробував зберегти модель 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. Це застосовно до інших ОРМ (Джанго тощо) загалом
  2. Крім того, якщо ви зрозуміли, як json.dumpsпрацює, це рішення також працює і з Python (sans ORM)

Будь-які питання, будь ласка, опублікуйте в розділі коментарів. Дякую!


1

Ця функція використовує рекурсію для ітерації над кожною частиною словника, а потім викликає метод 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):
        return obj
    else:
        return obj.__repr__()


0

Я придумав власне рішення. Використовуйте цей метод, передайте будь-який документ ( 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

0

Я вирішив використовувати декоратори для вирішення проблеми серіалізації об'єкта 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 у звичайний спосіб (без зазначення ключового слова за замовчуванням) для серіалізації даних, що містять об’єкти часу дати. Код серіалізатора дат автоматично викликається для json.dumps та json.dump.


0

Мені найбільше сподобався метод Lost Koder. У мене виникли проблеми, коли я намагався серіалізувати складніші об'єкти, члени учасників / методи яких не піддаються серіалізації. Ось моя реалізація, яка працює над іншими об’єктами:

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)

0

Якщо ви зможете встановити пакет, я рекомендую спробувати кріп , який добре працював для мого проекту. Приємна річ у цьому пакеті полягає в тому, що він має той самий інтерфейс 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, кортеж, список, dict, файл, буфер, вбудований, як старий, так і новий клас стилів, екземпляри класів старого та нового стилю, set, freedset, масив , функції, винятки

dill також можна маринувати більш "екзотичні" стандартні типи:

функції з урожайністю, вкладеними функціями, лямбдашами, клітинками, методом, необмеженим методом, модулем, кодом, методом перетягування, dictproxy, methoddescriptor, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, фрагмент, не застосовується, еліпсис, кинути

dill поки не можна маринувати ці стандартні типи:

кадр, генератор, трекбек


0

Я не бачу тут жодної згадки про серійні версії чи зворотні версії, тому я опублікую своє рішення, яке я трохи використовував. Я, мабуть, маю багато чого навчитися, зокрема, Java та Javascript, ймовірно, більш зрілі, ніж я, але тут ідеться

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


0

Щоб додати інший варіант: Ви можете використовувати 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')

0

На додаток до відповіді Онура , можливо, ви хочете мати справу з типом дати, як показано нижче.
(для того, щоб обробити: '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)

0

Спочатку нам потрібно зробити наш об’єкт сумісним 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

0

Спираючись на Quinten Cabo «S відповідь :

def sterilize(obj):
    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()}
    elif hasattr(obj, '__iter__') and callable(obj.__iter__):
        return [sterilize(v) for v in obj]
    elif hasattr(obj, '__dict__'):
        return {k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']}
    else:
        return repr(obj)

Відмінності є

  1. Працює для будь-якого ітерабельного, а не просто listтаtuple ( працює для масивів NumPy тощо)
  2. Працює для динамічних типів (тих, що містять a __dict__ ).
  3. Включає в себе власні типи floatі Noneтому вони не перетворюються в рядки.

Залишене як вправу для читача - це обробляти __slots__, класи, які є ітерабельними і мають членів, класи, які є словниками, а також мають члени тощо.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.