Как сделать класс JSON сериализуемым - PullRequest
650 голосов
/ 22 сентября 2010

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

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

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

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

json.dumps()

без ошибок (FileItem instance at ... is not JSON serializable)

Ответы [ 25 ]

5 голосов
/ 04 апреля 2016

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

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

Обратите внимание, что они по-прежнему не могут печатать методы объекта.

5 голосов
/ 09 октября 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.

4 голосов
/ 27 июня 2018

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

код

# 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 голосов
/ 17 июля 2015
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)
2 голосов
/ 18 января 2019

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

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))
2 голосов
/ 07 октября 2014

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"}'
2 голосов
/ 10 ноября 2016

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

1 голос
/ 30 июля 2018

Я столкнулся с этой проблемой, когда попытался сохранить модель 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 (Джанго и т. Д.) В целом
  2. Кроме того, если вы поняли, как работает json.dumps, это решение также работает с Python (без ORM) в целом

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

1 голос
/ 10 июля 2013

Вот мои 3 цента ...
Это демонстрирует явную сериализацию JSON для древовидного объекта Python.
Примечание: если вы действительно хотите какой-то код, подобный этому, вы можете использовать витой FilePath class.

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)
0 голосов
/ 17 июля 2014

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

https://github.com/Toubs/PyJSONSerialization/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...