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

525 голосов
/ 21 марта 2013

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

.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"
}
472 голосов
/ 22 сентября 2010

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

>>> 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>
>>> 
146 голосов
/ 23 декабря 2011

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

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

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

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

59 голосов
/ 03 июля 2015

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

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

37 голосов
/ 16 июня 2012

Другой вариант - обернуть 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 голосов
/ 27 января 2015

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

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
24 голосов
/ 18 февраля 2016

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

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

10 голосов
/ 17 июня 2015
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))
9 голосов
/ 19 декабря 2018

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

import jsons

a_dict = jsons.dump(your_object)

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

a_str = jsons.dumps(your_object)

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

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