首页 文章

如何使类JSON可序列化

提问于
浏览
551

如何使Python类可序列化?

一个简单的类:

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

我该怎么做才能得到输出:

json.dumps()

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

22 回答

  • 22

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

  • 27

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

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

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

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

    对于更复杂的类,您可以考虑使用工具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)

  • 1

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

    如果您的类只是基本数据表示,那么这种方法很有效,对于您可以始终明确设置键的棘手问题 .

  • -1

    另一种选择是将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"}'
    
  • 32

    我喜欢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)
    
  • 2

    前几天我遇到了这个问题并实现了一个更通用的Python编译器版本,可以 handle nested objectsinherited fields

    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"
    }
    
  • 0

    只需将 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() .

  • 4
    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))
    
  • 0

    json 在它可以打印的对象方面是有限的,并且 jsonpickle (你可能需要一个 pip install jsonpickle )是有限的,它可以改变,我仍然找不到比以下更直的方式:

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

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

  • 3

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

  • 5
    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)
    
  • 49

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

    2018-06-27

  • 3

    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

    这是我的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)
    
  • 1

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

    pip install json-tricks
    

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

    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 可以导入并且没有以不兼容的方式更改 . You'll get back an instance ,不是一些字典或其他东西,它应该与您倾销的副本完全相同 .

    如果要自定义(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数组的序列化,日期和时间,有序 Map ,以及在json中包含注释的能力 .

    免责声明:我创建了json_tricks,因为我遇到了和你一样的问题 .

  • 119

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

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

    我的解决方案的关键是通过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 投诉,只需添加代码来处理unserializable-to-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,但我认为:

    • 一般适用于其他ORM(Django等)

    • 此外,如果您了解 json.dumps 如何工作,此解决方案也适用于Python(也称为ORM)

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

  • 1

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

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

  • 419

    我提出了自己的解决方案 . 使用此方法,传递任何文档(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
    
  • 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序列化程序代码 .

  • 9

    我最喜欢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)
    

相关问题