it-swarm.com.ru

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

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

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

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

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

json.dumps()

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

579
Sergey

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

>>> 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>
>>> 
437
Manoj Govindan

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

.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"
}
490
Onur Yıldırım

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

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

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

(ССЫЛКА НА JSONPICKLE В PYPI)

130
gecco

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

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

54
andyhasit

Другой вариант - обернуть 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"}'
34
Paulo Freitas

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

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

Я столкнулся с этой проблемой на днях и реализовал более общую версию 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"
}
23
tobigue

Просто добавьте метод 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() в вашем проекте.

17
Fancy John
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))
9
tryer3000

Этот класс может сделать свое дело, он конвертирует объект в стандартный 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.

5
Lost Koder

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

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

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

5
ribamar

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

Код

# 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
Martin Thoma
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)
3
rectangletangle

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
matthewlent

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

import jsons

a_dict = jsons.dump(your_object)

Или, если вы хотите строку:

a_str = jsons.dumps(your_object)

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

a_dict = your_object.json
2
R H

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

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

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

1
sivabudh

Вот мои 3 цента ...
Это демонстрирует явную сериализацию json для древовидного объекта python.
Примечание. Если вам действительно нужен какой-то подобный код, вы можете использовать класс twisted 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
Dan Brough

Если вы можете установить пакет, я бы порекомендовал попробовать dill , который отлично работал для моего проекта. Приятной особенностью этого пакета является то, что он имеет тот же интерфейс, что и 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, файл, буфер, встроенный, как старый, так и новый класс стилей, экземпляры старых и новых классов стилей, set, frozenset, array, функции, исключения 

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

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

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

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

0
thedavidmo

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

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
Dewsworld

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

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

0
Tobi

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

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

Мне больше всего понравился метод 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
Will Charlton

Я решил использовать декораторы для решения проблемы с сериализацией объекта 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
John Moore