关于python:如何使类json可序列化

How to make a class JSON serializable

如何使python类可序列化?

一个简单的类:

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

我应该怎么做才能获得以下输出:

1
json.dumps()

无误(FileItem instance at ... is not JSON serializable)


Here is a simple solution for a simple feature:

.toJSON() Method

Instead of a JSON serializable class, implement a serializer method:

1
2
3
4
5
6
import json

class Object:
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__,
            sort_keys=True, indent=4)

所以您只需调用它来序列化:

1
2
3
4
5
6
7
me = Object()
me.name ="Onur"
me.age = 35
me.dog = Object()
me.dog.name ="Apollo"

print(me.toJSON())

意志产出:

1
2
3
4
5
6
7
{
   "age": 35,
   "dog": {
       "name":"Apollo"
    },
   "name":"Onur"
}


你对预期产出有什么想法吗?例如,这能做到吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> f  = FileItem("/foo/bar")
>>> magic(f)
'{"fname":"/foo/bar
<div class="suo-content">[collapse title=""]<ul><li>使用<wyn>__dict__</wyn>在所有情况下都不起作用。如果在对象实例化后未设置属性,则可能无法完全填充<wyn>__dict__</wyn>。在上面的例子中,您是可以的,但是如果您有您也想要编码的类属性,那么这些属性将不会在<wyn>__dict__</wyn>中列出,除非它们在类'
<wyn>__init__</wyn>调用中被修改过,或者在对象被实例化后以其他方式被修改过。</li><li>+1,但是用作对象钩子的<wyn>from_json()</wyn>函数应该有一个<wyn>else: return json_object</wyn>语句,这样它也可以处理一般对象。</li><li>@如果你在一个新的风格类上使用<wyn>__slots__</wyn>,那么krishardy <wyn>__dict__</wyn>也不起作用。</li><li>这比使用cpickle快吗?</li><li>您可以如上所述使用自定义<wyn>JSONEncoder</wyn>创建自定义协议,例如检查<wyn>__json_serializable__</wyn>方法的存在并调用它以获取对象的JSON可序列化表示。这将与其他的Python模式保持一致,如<wyn>__getitem__</wyn><wyn>__str__</wyn><wyn>__eq__</wyn><wyn>__len__</wyn></li><li>对python来说是新的吗?我们可以使用这些库(<wyn>json</wyn><wyn>simplejson</wyn>来序列化/反序列化python开发人员(如pandas数据帧、系列等)广泛使用的对象吗?</li><li><wyn>__dict__</wyn>也不会递归工作,例如,如果对象的属性是另一个对象。</li><li>如果给定的数据是非标准的,那么<wyn>JSONEncoder</wyn>只调用子类的<wyn>default</wyn>方法。</li><li>@Mahesha999对数据帧、序列等使用df.to_json()方法</li></ul>[/collapse]</div><hr>
<p>
For more complex classes you could consider the tool jsonpickle:
</p>

<blockquote>
  <p>
jsonpickle is a Python library for serialization and deserialization of complex Python objects to and from JSON.
</p>
 
  <p>
The standard Python libraries for encoding Python into JSON, such as the stdlib’s json, simplejson, and demjson, can only handle Python primitives that have a direct JSON equivalent (e.g. dicts, lists, strings, ints, etc.). jsonpickle builds on top of these libraries and allows more complex data structures to be serialized to JSON. jsonpickle is highly configurable and extendable–allowing the user to choose the JSON backend and add additional backends.
</p>
</blockquote>

<p>
(link to jsonpickle on PyPi)
</p>

<div class="suo-content">[collapse title=""]<ul><li>来自C,这是我所期待的。一条简单的单行线,不会弄乱课堂。</li><li>jsonpickle太棒了。它非常适合于具有许多级别的类的大型、复杂、杂乱的对象。</li><li>是否有将此保存到文件的正确方法示例?文档只显示如何对<wyn>jsonpickle</wyn>对象进行编码和解码。此外,这无法解码包含熊猫数据帧的听写。</li><li>@用户5359531可以使用<wyn>obj = jsonpickle.decode(file.read())</wyn><wyn>file.write(jsonpickle.encode(obj))</wyn></li><li>针对Django的一个问题是:使用jsonpickle序列化会话数据是否具有与pickle相同的漏洞?(如本文所述docs.djangoproject.com/en/1.11/topics/http/sessions/&hellip;)</li><li>@鲍尔伯曼是的。</li></ul>[/collapse]</div><hr><P>大多数答案都涉及到更改对json.dumps()的调用,这并不总是可能的或可取的(例如,它可能发生在框架组件中)</P><P>如果您希望能够按原样调用json.dumps(obj),那么一个简单的解决方案就是从dict继承:</P>[cc lang="python"]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

如果您的类只是基本的数据表示,那么对于更棘手的事情,您总是可以显式地设置键。


Another option is to wrap JSON dumping in its own class:

1
2
3
4
5
6
7
8
import json

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

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

或者,更好的是,从JsonSerializable类中对fileitem类进行子类化:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname":"/foo/bar
<div class="suo-content">[collapse title=""]<ul><li>嗨,我真的不喜欢这种"自定义编码器"方法,如果您能使类JSON可串行化就更好了。我试着,试着,什么也不试。你知道怎么做吗?问题是,JSON模块针对内置的Python类型测试类,甚至还说定制类可以生成编码器:)。它能被伪造吗?所以我可以对我的类做一些事情,让它的行为像简单的列表到JSON模块?我尝试子类检查和实例检查,但什么都没有。</li><li>@如果所有类属性值都是可序列化的,并且您不介意黑客攻击,那么您可以从主要类型(可能是dict)继承肾上腺素。您也可以使用jsonpickle或json_技巧,而不是标准的(仍然是自定义编码器,但不需要编写或调用)。前者对实例进行pickle处理,后者将其存储为属性dict,您可以通过实现<wyn>__json__encode__</wyn>/<wyn>__json_decode__</wyn>(公开:我做了最后一个)。</li></ul>[/collapse]</div><hr>
<p>
I like Onur'
s answer but would expand to include an optional <wyn>toJSON()</wyn> method for objects to serialize themselves:
</p>

[cc lang="python"]def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)


I came across this problem the other day and implemented a more general version of an Encoder for Python objects that can handle nested objects and inherited fields:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

例子:

1
2
3
4
5
6
class C(object):
    c ="NO"
    def to_json(self):
        return {"c":"YES
<div class="
suo-content">[collapse title=""]<ul><li>虽然这有点旧…我面临一些循环导入错误。所以,我不是在最后一行中使用<wyn>return obj</wyn>,而是使用<wyn>return super(ObjectEncoder, self).default(obj)</wyn>。这里参考</li></ul>[/collapse]</div><hr><P>只需向类中添加<wyn>to_json</wyn>方法,如下所示:</P>[cc lang="python"]def to_json(self):
  return self.message # or how you want it to be serialized

把这段代码(从这个答案)添加到最上面的某个地方:

1
2
3
4
5
6
7
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模块时,它将monkey补丁,所以jsonEncoder.default()自动检查特殊的"to_json()"方法,并使用它对找到的对象进行编码。

正如奥纳所说,但这次您不必更新项目中的每个json.dumps()


1
2
3
4
5
6
7
8
9
10
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', '[email protected]')))

如果使用标准json,则需要定义default函数

1
2
3
4
5
import json
def default(o):
    return o._asdict()

print(json.dumps(User('alice', '[email protected]'), default=default))


json is limited in terms of objects it can print, and jsonpickle (you may need a pip install jsonpickle) is limited in terms it can't indent text. If you would like to inspect the contents of an objecth whose class you can't change, I still couldn't find a straighter way than:

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

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


这个类可以做到这一点,它将对象转换为标准JSON。

1
2
3
4
5
6
7
import json


class Serializer(object):
    @staticmethod
    def serialize(object):
        return json.dumps(object, default=lambda o: o.__dict__.values()[0])

用途:

1
Serializer.serialize(my_object)

python2.7python3工作。


如果你用的是python3.5+,你可以用jsons。它将把对象(及其所有属性递归地)转换为dict。

1
2
3
import jsons

a_dict = jsons.dump(your_object)

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

1
a_str = jsons.dumps(your_object)

或者如果您的类实现了jsons.JsonSerializable

1
a_dict = your_object.json


雅拉科给出了一个非常清楚的回答。我需要解决一些小问题,但这是可行的:

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 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):

  • to_json266595 2018-06-27
  • toJSON号:2018-06-27 96307
  • __json__2018-06-27:8504
  • for_json号:6937,2018-06-27

反序列化(json->python):

  • from_json号:2018-06-27 226101

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
<div class="suo-content">[collapse title=""]<ul><li>From Doc:参数<wyn>default(obj)</wyn>是一个函数,应该返回obj的可序列化版本或引发typeerror。默认的<wyn>default</wyn>只会引发类型错误。</li></ul>[/collapse]</div><p><center>[wp_ad_camp_3]</center></p><hr><P>jsonweb似乎是我最好的解决方案。请参阅http://www.jsonweb.info/en/latest/</P>[cc lang="python"]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
<div class="
suo-content">[collapse title=""]<ul><li>它对嵌套对象是否有效?包括解码和编码</li></ul>[/collapse]</div><hr><P>这对我很有效:</P>[cc lang="python"]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__

然后

1
2
class FileItem(JsonSerializable):
    ...

1
log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))

如果您不介意为它安装一个包,您可以使用JSON技巧:

1
pip install json-tricks

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

1
2
from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)

将给予

1
2
3
4
5
6
7
8
9
10
11
12
{
       "__instance_type__": [
               "module_name.test_class",
               "MyTestCls"
        ],
       "attributes": {
               "attr":"val",
               "dct_attr": {
                       "hello": 42
                }
        }
}

基本上就是这样!

一般来说,这会很有效。也有一些例外,例如,如果在__new__中发生特殊的事情,或者更多的元类魔法正在发生。

显然,加载也有效(否则重点是什么):

1
2
from json_tricks import loads
json_str = loads(json_str)

这确实假定可以导入module_name.test_class.MyTestCls,并且没有以不兼容的方式进行更改。您将得到一个实例,而不是一些字典或其他东西,它应该是与您转储的实例相同的副本。

如果您想自定义某个对象如何(反)序列化,可以向类中添加特殊方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
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_技巧,因为我和你有相同的问题。


这是我的3美分…
这演示了类似于python对象的树的显式JSON序列化。
注意:如果你真的想要这样的代码,你可以使用TwistedFielPATH类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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)

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

经过一段时间的挣扎,这里是一般的解决办法。

我的解决方案的关键是浏览python的源代码,认识到代码文档(在这里描述)已经解释了如何扩展现有的json.dumps以支持其他数据类型。

假设您当前的模型包含一些不能序列化到JSON的字段,并且包含JSON字段的模型最初如下所示:

1
2
class SomeClass(Model):
    json_field = JSONField()

像这样定义一个自定义JSONEncoder

1
2
3
4
5
6
7
8
9
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中使用它,如下所示:

1
2
class SomeClass(Model):
    json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)

关键是上面的default(self, obj)方法。对于您从python收到的每个单独的... is not JSON serializable投诉,只需添加代码来处理不可销售的json类型(如Enumdatetime)。

例如,以下是我如何支持从Enum继承的类:

1
2
3
4
5
6
7
8
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可序列化对象:

1
2
3
peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)

虽然上面的代码(有点)特定于Peewee,但是我认为:

  • 一般适用于其他形式(Django等)
  • 另外,如果您了解json.dumps的工作原理,这个解决方案通常也可以与python(sans-orm)一起工作。
  • 如有任何问题,请发表在评论部分。谢谢!


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    #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来序列化包含日期时间对象的数据。对于json.dumps和json.dump,将自动调用日期时间序列化程序代码。


    我最喜欢Lost Koder的方法。我在试图序列化成员/方法不可序列化的更复杂的对象时遇到问题。下面是我在更多对象上工作的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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)

    我想出了自己的解决办法。使用此方法,传递要序列化的任何文档(dict、list、objectid等)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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

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

    https://github.com/toubs/pyjsonserialization网站/


    如果你能安装一个软件包,我建议你尝试一下Dill,它对我的项目很好。这个包的一个好处是它具有与pickle相同的接口,所以如果您已经在项目中使用了pickle,那么您可以简单地在dill中进行替换,查看脚本是否运行,而不更改任何代码。所以这是一个非常便宜的解决方案!

    (完全反披露:我与DILL项目没有任何关联,也从未参与过该项目。)

    安装程序包:

    1
    pip install dill

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

    1
    2
    # import pickle
    import dill as pickle

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

    dill可以也不能从项目页序列化的数据类型的一些细节:

    dill can pickle the following standard types:

    none, type, bool, int, long, float, complex, str, unicode, tuple,
    list, dict, file, buffer, builtin, both old and new style classes,
    instances of old and new style classes, set, frozenset, array,
    functions, exceptions

    dill can also pickle more ‘exotic’ standard types:

    functions with yields, nested functions, lambdas, cell, method,
    unboundmethod, module, code, methodwrapper, dictproxy,
    methoddescriptor, getsetdescriptor, memberdescriptor,
    wrapperdescriptor, xrange, slice, notimplemented, ellipsis, quit

    dill cannot yet pickle these standard types:

    frame, generator, traceback


    这个问题有很多解决办法。"objdict(pip安装objdict)是另一个。重点是提供类似于javascript的对象,这些对象也可以像字典一样处理从JSON加载的数据,但是还有其他一些功能也很有用。这为原始问题提供了另一种解决方案。