如何使类JSON可序列化

时间:2010-09-22 11:52:20

标签: python json serialization

如何使Python类可序列化?

一个简单的课程:

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

如何才能获得输出:

json.dumps()

没有错误(FileItem instance at ... is not JSON serializable

33 个答案:

答案 0 :(得分:517)

以下是一个简单功能的简单解决方案:

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

答案 1 :(得分:461)

您对预期产量有所了解吗?对于例如这会吗?

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

然后您将此课程作为cls kwarg传递到json.dumps()方法:

json.dumps(cls=MyEncoder)

如果您还想解码,则必须向JSONDecoder课程提供自定义object_hook。例如,

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

答案 2 :(得分:140)

对于更复杂的类,您可以考虑使用工具jsonpickle

  

jsonpickle是一个Python库,用于将复杂Python对象与JSON进行序列化和反序列化。

     

用于将Python编码为JSON的标准Python库,例如stdlib的json,simplejson和demjson,只能处理具有直接JSON等效的Python原语(例如dicts,lists,strings,int等)。 jsonpickle构建在这些库之上,允许将更复杂的数据结构序列化为JSON。 jsonpickle具有高度可配置性和可扩展性,允许用户选择JSON后端并添加额外的后端。

(link to jsonpickle on PyPi)

答案 3 :(得分:58)

大多数答案都涉及将调用更改为 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

如果你的类只是基本的数据表示,那么这是有效的,对于你可以随时明确设置键的更棘手的东西。

答案 4 :(得分:37)

另一种选择是将JSON转储包装在自己的类中:

import json

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

    def __repr__(self):
        return json.dumps(self.__dict__)

或者更好的是,从JsonSerializable类继承FileItem类:

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

答案 5 :(得分:33)

我喜欢Onur's answer,但会扩展为包含可选的toJSON()方法,以便对象自行序列化:

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

答案 6 :(得分: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"
}

答案 7 :(得分:21)

只需将to_json方法添加到您的课程中,如下所示:

def to_json(self):
  return self.message # or how you want it to be serialized

并将此代码(来自this answer添加到所有内容的顶部:

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()” 方法,并使用它来编码对象,如果找到。

就像Onur说的那样,但这次你不必更新项目中的每个json.dumps()

答案 8 :(得分:9)

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 :(得分:8)

如果您使用的是Python3.5 +,则可以使用jsons。它将把您的对象(及其所有属性递归地)转换成字典。

import jsons

a_dict = jsons.dump(your_object)

或者如果您想要一个字符串:

a_str = jsons.dumps(your_object)

或者如果您的班级实施了jsons.JsonSerializable

a_dict = your_object.json

答案 10 :(得分:5)

这个类可以做到这一点,它将对象转换为标准的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.7python3工作。

答案 11 :(得分:5)

json在可以打印的对象方面受到限制,jsonpickle(您可能需要pip install jsonpickle)受限制,因为它无法缩进文字。如果你想检查一个你无法改变的类的对象的内容,我仍然找不到比以下更直接的方式:

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

请注意,仍然无法打印对象方法。

答案 12 :(得分:4)

如许多其他答案所述,您可以将函数传递给json.dumps,以将默认情况下不支持的类型之一的对象转换为支持的类型。令人惊讶的是,它们中没有一个提到最简单的情况,即使用内置函数vars将对象转换为包含所有属性的字典:

json.dumps(obj, default=vars)

如果您需要某些类型的特定序列化(例如,排除某些属性),则可以使用自定义函数或其他答案中所描述的JSONEncoder

答案 13 :(得分:4)

jaraco给出了一个非常简洁的答案。我需要修复一些小问题,但这可行:

代码

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

答案 14 :(得分: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)

答案 15 :(得分: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"}'

答案 16 :(得分:2)

class DObject(json.JSONEncoder):
    def delete_not_related_keys(self, _dict):
        for key in ["skipkeys", "ensure_ascii", "check_circular", "allow_nan", "sort_keys", "indent"]:
            try:
                del _dict[key]
            except:
                continue

    def default(self, o):
        if hasattr(o, '__dict__'):
            my_dict = o.__dict__.copy()
            self.delete_not_related_keys(my_dict)
            return my_dict
        else:
            return o

a = DObject()
a.name = 'abdul wahid'
b = DObject()
b.name = a

print(json.dumps(b, cls=DObject))

答案 17 :(得分:2)

Quinten Caboanswer为基础:

def sterilize(obj):
    """Make an object more ameniable to dumping as json
    """
    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()}
    list_ret = []
    dict_ret = {}
    for a in dir(obj):
        if a == '__iter__' and callable(obj.__iter__):
            list_ret.extend([sterilize(v) for v in obj])
        elif a == '__dict__':
            dict_ret.update({k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']})
        elif a not in ['__doc__', '__module__']:
            aval = getattr(obj, a)
            if type(aval) in (str, float, int, bool, type(None)):
                dict_ret[a] = aval
            elif a != '__class__' and a != '__objclass__' and isinstance(aval, type):
                dict_ret[a] = sterilize(aval)
    if len(list_ret) == 0:
        if len(dict_ret) == 0:
            return repr(obj)
        return dict_ret
    else:
        if len(dict_ret) == 0:
            return list_ret
    return (list_ret, dict_ret)

区别是

  1. 适用于任何可迭代的方法,而不仅仅是listtuple(适用于NumPy数组等)
  2. 适用于动态类型(包含__dict__的动态类型。
  3. 包括本机类型floatNone,因此它们不会转换为字符串。
  4. 拥有__dict__和成员的课程将主要用于 (如果__dict__和成员名称发生冲突,则您只会得到一个-可能是成员)
  5. 属于列表且具有成员的类看起来像列表的元组和字典
  6. Python3(isinstance()调用 可能是唯一需要更改的东西)

答案 18 :(得分:2)

如果您不介意为其安装软件包,可以使用json-tricks

pip install json-tricks

之后,您只需要从dump(s)而不是json导入json_tricks,它通常可以正常工作:

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并且不以不兼容的方式更改。 您将获得一个实例,而不是某些字典或其他内容,它应该与您转储的实体完全相同。

如果要自定义(de)序列化的内容,可以在类中添加特殊方法,如下所示:

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

仅序列化部分属性参数,作为示例。

作为免费赠品,您可以获得(de)numpy数组的序列化,日期和时间。时间,有序地图,以及在json中包含评论的能力。

免责声明:我创建了json_tricks,因为我遇到了与您相同的问题。

答案 19 :(得分:2)

这对我来说效果很好:

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

答案 20 :(得分: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, float):
        return obj
    else:
        return obj.__repr__()

答案 21 :(得分:1)

当我尝试将Peewee的模型存储到PostgreSQL JSONField中时遇到了这个问题。

经过一段时间的努力,这是一般的解决方法。

我的解决方案的关键是浏览Python的源代码,并意识到代码文档(描述为here)已经说明了如何扩展现有的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)方法。对于您从Python收到的每一个... is not JSON serializable投诉,只需添加代码即可处理无法序列化为JSON的类型(例如Enumdatetime

例如,这是我如何支持从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)

任何问题,请在评论部分发布。谢谢!

答案 22 :(得分:1)

这是我的3美分......
这演示了树状python对象的显式json序列化 注意:如果您真的想要这样的代码,可以使用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)

答案 23 :(得分:0)

如果您能够安装软件包,建议您尝试dill,它对我的​​项目来说效果很好。关于此软件包的一个好处是,它具有与pickle相同的接口,因此,如果您已经在项目中使用过pickle,则可以简单地替换为dill并查看脚本运行,而无需更改任何代码。因此,这是一种非常便宜的解决方案!

(全面反公开:我与莳萝项目毫无关系,也从未参与过该项目。)

安装软件包:

pip install dill

然后编辑代码以导入dill而不是pickle

# import pickle
import dill as pickle

运行脚本,看看它是否有效。 (如果这样做,您可能希望清理代码,以使您不再隐藏pickle模块名称!)

dill可以和不可以从the project page进行序列化的数据类型的一些细节:

  

dill可以腌制以下标准类型:

     

none,type,bool,int,long,float,complex,str,unicode,tuple,   列表,字典,文件,缓冲区,内置,新旧样式类,   新旧样式类的实例,集合,frozenset,数组,   函数,异常

     

dill也可以腌制更多“奇特”标准类型:

     

具有收益的函数,嵌套函数,lambda,单元格,方法,   unboundmethod,模块,代码,methodwrapper,dictproxy,   methoddescriptor,getsetdescriptor,memberdescriptor,   wrapperdescriptor,xrange,slice,未实现,省略号,退出

     

dill仍无法腌制以下标准类型:

     

框架,生成器,回溯

答案 24 :(得分: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

答案 25 :(得分:0)

除了Onur's answer之外,您可能还需要处理如下所示的日期时间类型。
(为了处理:“ 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)

答案 26 :(得分:0)

要添加另一个选项:您可以使用import org.junit.Test包和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)

答案 27 :(得分:0)

我在这里没有提到串行版本控制或反向兼容,因此我将发布我已经使用了一段时间的解决方案。我可能有很多东西要学,特别是Java和Javascript在这里可能比我更成熟,但是在这里

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

答案 28 :(得分: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)

答案 29 :(得分:0)

我选择使用装饰器来解决日期时间对象序列化问题。 这是我的代码:

#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(不指定default关键字)来序列化包含日期时间对象的数据。 json.dumps和json.dump会自动调用datetime序列化程序代码。

答案 30 :(得分:0)

这是一个小型库,它将一个对象及其所有子对象序列化为JSON,并将其解析回来:

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

答案 31 :(得分: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

答案 32 :(得分:-1)

有很多方法可以解决这个问题。 &#39; ObjDict&#39; (pip install objdict)是另一个。重点在于提供类似javascript的对象,它们也可以像字典一样最好地处理从JSON加载的数据,但是还有其他功能也很有用。这为原始问题提供了另一种替代解决方案。