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

834

Как сделать класс 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

Без ошибки

Сергей
источник
31
К сожалению, все ответы, кажется, отвечают на вопрос "Как мне сериализовать класс?" а не вопрос действия "Как сделать сериализуемый класс?" Эти ответы предполагают, что вы выполняете сериализацию самостоятельно, а не передаете объект другому модулю, который его сериализует.
Кайл Делани
Если вы используете 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__также не будет работать рекурсивно, например, если атрибут вашего объекта является другим объектом.
Нил
635

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

.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__)
Онур Йылдырым
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, simplejson и demjson в stdlib, могут обрабатывать только примитивы Python, имеющие прямой эквивалент JSON (например, dicts, списки, строки, целые числа и т. Д.). jsonpickle основывается на этих библиотеках и позволяет сериализовать более сложные структуры данных в JSON. jsonpickle обладает широкими возможностями настройки и расширения, что позволяет пользователю выбирать бэкэнд JSON и добавлять дополнительные бэкэнды.

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

GECCO
источник
32
Исходя из C #, это то, что я ожидал. Простой лайнер и не возиться с классами.
Джертер
2
Jsonpickle это круто. Он отлично работал для огромного, сложного, грязного объекта со многими уровнями классов
wisbucky
Есть ли пример правильного способа сохранить это в файл? Документация только показывает, как кодировать и декодировать jsonpickleобъект. Кроме того, это не было в состоянии расшифровать диктовку, содержащую кадры данных панд.
user5359531
3
@ user5359531 вы можете использовать obj = jsonpickle.decode(file.read())и file.write(jsonpickle.encode(obj)).
Килиан Батцнер
1
Вопрос специально для django: использует ли jsonpickle для сериализации данных сеанса ту же уязвимость, что и pickle? (как описано здесь docs.djangoproject.com/en/1.11/topics/http/sessions/… )?
Пол
90

Большинство ответов включают изменение вызова 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

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

andyhasit
источник
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 () по какой-то другой причине, чем его не было) ... сбоя, который потенциально может привести к повреждению данных.
Спасибо
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? Я пробую subclasscheck и instancecheck, но ничего.
Боян Радоевич
@ADRENALIN Вы можете наследовать от первичного типа (возможно, dict), если все значения атрибутов класса являются сериализуемыми и вы не против хаков. Вы также можете использовать 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.
Юнвэй Ву
24

Я столкнулся с этой проблемой на днях и реализовал более общую версию Encoder для объектов 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"
}
tobigue
источник
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
RH
источник
3
Если вы можете использовать Python 3.7+, я обнаружил, что самое чистое решение для преобразования классов Python в dicts и строки JSON (и наоборот) - это смешать jsonsбиблиотеку с классами данных . Пока все хорошо для меня!
Ruluk
3
Это внешняя библиотека, не встроенная в стандартную установку Python.
Ноумен
только для класса с атрибутом slots
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))
tryer3000
источник
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)

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

Ribamar
источник
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.

Потерянный кодер
источник
Мне понравился этот метод больше всего. Я столкнулся с проблемами при попытке сериализовать более сложные объекты, члены / методы которых не сериализуемы. Вот моя реализация, которая работает с другими объектами: `` `class Serializer (object): @staticmethod def serialize (obj): def check (o): для k, v в 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)
rectangletangle
источник
From 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__свойство не используется.

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

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

Сериализация (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))
jmhostalet
источник
3

Если вы не против установить пакет для него, вы можете использовать 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

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

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

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

отметка
источник
1
Я только что проверил json_tricks, и это работало, чтобы украсить (в 2019 году).
pauljohn32
2

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"}'
matthewlent
источник
Это хорошо работает для вложенных объектов? Включая декодирование и кодирование
Симона Зандара
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, просто добавьте код для обработки типа unserializable-to-JSON (например, Enumor 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) в целом тоже

Любые вопросы, пожалуйста, оставляйте в разделе комментариев. Спасибо!

sivabudh
источник
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__()
Quinten Cabo
источник
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
Dewsworld
источник
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 обычным способом (без указания ключевого слова по умолчанию) для сериализации данных, которые содержат объекты даты и времени. Код сериализатора datetime автоматически вызывается для 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, тип, bool, int, long, float, complex, str, unicode, tuple, список, dict, файл, буфер, встроенный, классы старого и нового стилей, экземпляры классов старого и нового стилей, набор, frozenset, массив , функции, исключения

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

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

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

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

thedavidmo
источник
0

Я не вижу здесь упоминания о последовательном версионировании или backcompat, поэтому я опубликую свое решение, которое я использовал немного. Мне, наверное, есть чему поучиться, в частности, 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')
machinekoder
источник
0

В дополнение к ответу Onur , вы, возможно, захотите иметь дело с типом 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)
Марк Чой
источник
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. Работает для динамических типов (те, которые содержат __dict__).
  3. Включает в себя собственные типы floatи Noneпоэтому они не преобразуются в строки.

В качестве упражнения для читателя __slots__оставлено обрабатывать классы, которые являются итеративными и имеют члены, классы, являющиеся словарями, а также членами и т. Д.

mheyman
источник