【问题标题】:Serializing class instance to JSON将类实例序列化为 JSON
【发布时间】:2012-04-20 19:00:21
【问题描述】:

我正在尝试创建一个类实例的 JSON 字符串表示,但遇到了困难。假设这个类是这样构建的:

class testclass:
    value1 = "a"
    value2 = "b"

对 json.dumps 的调用是这样的:

t = testclass()
json.dumps(t)

失败并告诉我测试类不是 JSON 可序列化的。

TypeError: <__main__.testclass object at 0x000000000227A400> is not JSON serializable

我也尝试过使用 pickle 模块:

t = testclass()
print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))

它给出了类实例信息,而不是类实例的序列化内容。

b'\x80\x03c__main__\ntestclass\nq\x00)\x81q\x01}q\x02b.'

我做错了什么?

【问题讨论】:

  • 使用一行,s = json.dumps(obj, default=lambda x: x.__dict__),序列化对象的实例变量(self.value1self.value2,...)。它是最简单和最直接的方式。它将序列化嵌套的对象结构。 default 函数在任何给定对象不能直接序列化时调用。你也可以看看我下面的回答。我发现流行的答案不必要地复杂,这在很久以前可能是正确的。
  • 您的testclass 没有__init__() 方法,因此所有实例将共享类语句中定义的相同的两个类属性(value1value2)。您了解类和类的实例之间的区别吗?
  • 这个github.com/jsonpickle/jsonpickle 有一个python 库(评论因为答案在线程中太低并且无法访问。)

标签: python json serialization pickle


【解决方案1】:

基本问题是 JSON 编码器json.dumps() 默认只知道如何序列化一组有限的对象类型,所有内置类型。在此处列出:https://docs.python.org/3.3/library/json.html#encoders-and-decoders

一个好的解决方案是让您的类继承自 JSONEncoder,然后实现 JSONEncoder.default() 函数,并让该函数为您的类发出正确的 JSON。

一个简单的解决方案是在该实例的.__dict__ 成员上调用json.dumps()。那是一个标准的 Python dict,如果你的类很简单,它将是 JSON 可序列化的。

class Foo(object):
    def __init__(self):
        self.x = 1
        self.y = 2

foo = Foo()
s = json.dumps(foo) # raises TypeError with "is not JSON serializable"

s = json.dumps(foo.__dict__) # s set to: {"x":1, "y":2}

这篇博文讨论了上述方法:

    Serializing arbitrary Python objects to JSON using _dict_

当然,Python 提供了一个内置函数,可以为您访问.__dict__,称为vars()

所以上面的例子也可以这样写:

s = json.dumps(vars(foo)) # s set to: {"x":1, "y":2}

【讨论】:

  • 我试过这个。调用 json.dumps(t.__dict__) 的最终结果只是 {}。
  • 那是因为你的类没有.__init__()方法函数,所以你的类实例有一个空字典。换句话说,{} 是您的示例代码的正确结果。
  • 谢谢。这可以解决问题。我添加了一个没有参数的简单 init ,现在调用 json.dumps(t.__dict__) 以以下格式返回正确的数据: {"value2": "345", "value1": "123 "} 我以前看过这样的帖子,不确定我是否需要为成员自定义序列化程序,没有明确提到需要 init 或者我错过了。谢谢。
  • 这适用于单个类,但不适用于相关类对象
  • @NwawelAIroume:是的。如果您有一个对象,例如在列表中包含多个对象错误仍然是is not JSON serializable
【解决方案2】:

有一种方法对我很有效,您可以尝试一下:

json.dumps() 可以采用可选参数default,您可以在其中为未知类型指定自定义序列化函数,在我的例子中看起来像

def serialize(obj):
    """JSON serializer for objects not serializable by default json code"""

    if isinstance(obj, date):
        serial = obj.isoformat()
        return serial

    if isinstance(obj, time):
        serial = obj.isoformat()
        return serial

    return obj.__dict__

前两个 if 用于日期和时间序列化 然后为任何其他对象返回一个obj.__dict__

最终调用如下:

json.dumps(myObj, default=serialize)

当您序列化一个集合并且您不想为每个对象显式调用__dict__ 时,这特别好。这里会自动为您完成。

到目前为止对我来说效果很好,期待您的想法。

【讨论】:

  • 我收到NameError: name 'serialize' is not defined。有什么建议吗?
  • 非常好。仅适用于有插槽的类:try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict
【解决方案3】:

您可以在json.dumps()函数中指定default命名参数:

json.dumps(obj, default=lambda x: x.__dict__)

解释:

形成文档(2.73.6):

``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.

(适用于 Python 2.7 和 Python 3.x)

注意:在这种情况下,您需要 instance 变量而不是 class 变量,正如问题中的示例所尝试的那样。 (我假设提问者的意思是 class instance 是一个类的对象)

我首先从@phihag 的回答here 中了解到这一点。发现这是完成这项工作的最简单、最干净的方法。

【讨论】:

  • 这对我有用,但由于 datetime.date 成员我稍微改变了它:default=lambda x: getattr(x, '__dict__', str(x))
  • @Dakota 不错的解决方法; datetime.date 是一个 C 实现,因此它没有 __dict__ 属性。恕我直言,为了统一起见,datetime.date 应该拥有它...
【解决方案4】:

使用jsonpickle

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)

【讨论】:

    【解决方案5】:

    我只是这样做:

    data=json.dumps(myobject.__dict__)
    

    这不是完整的答案,如果你有某种复杂的对象类,你肯定不会得到所有东西。但是,我将它用于一些简单的对象。

    一个非常有效的方法是您从 OptionParser 模块中获得的“选项”类。 在这里,它与 JSON 请求本身一起出现。

      def executeJson(self, url, options):
            data=json.dumps(options.__dict__)
            if options.verbose:
                print data
            headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
            return requests.post(url, data, headers=headers)
    

    【讨论】:

    • 如果你没有在类中使用它,你可能想要删除 self。
    • 只要对象不由其他对象组成,就可以了。
    【解决方案6】:

    Python3.x

    根据我的知识,我能达到的最佳方法是这个。
    请注意,此代码也处理 set()。
    这种方法是通用的,只需要类的扩展(在第二个例子中)。
    请注意,我只是对文件执行此操作,但根据您的喜好修改行为很容易。

    然而这是一个编解码器。

    再做一点工作,您就可以用其他方式构建您的类。 我假设一个默认构造函数来实例化它,然后我更新类字典。

    import json
    import collections
    
    
    class JsonClassSerializable(json.JSONEncoder):
    
        REGISTERED_CLASS = {}
    
        def register(ctype):
            JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype
    
        def default(self, obj):
            if isinstance(obj, collections.Set):
                return dict(_set_object=list(obj))
            if isinstance(obj, JsonClassSerializable):
                jclass = {}
                jclass["name"] = type(obj).__name__
                jclass["dict"] = obj.__dict__
                return dict(_class_object=jclass)
            else:
                return json.JSONEncoder.default(self, obj)
    
        def json_to_class(self, dct):
            if '_set_object' in dct:
                return set(dct['_set_object'])
            elif '_class_object' in dct:
                cclass = dct['_class_object']
                cclass_name = cclass["name"]
                if cclass_name not in self.REGISTERED_CLASS:
                    raise RuntimeError(
                        "Class {} not registered in JSON Parser"
                        .format(cclass["name"])
                    )
                instance = self.REGISTERED_CLASS[cclass_name]()
                instance.__dict__ = cclass["dict"]
                return instance
            return dct
    
        def encode_(self, file):
            with open(file, 'w') as outfile:
                json.dump(
                    self.__dict__, outfile,
                    cls=JsonClassSerializable,
                    indent=4,
                    sort_keys=True
                )
    
        def decode_(self, file):
            try:
                with open(file, 'r') as infile:
                    self.__dict__ = json.load(
                        infile,
                        object_hook=self.json_to_class
                    )
            except FileNotFoundError:
                print("Persistence load failed "
                      "'{}' do not exists".format(file)
                      )
    
    
    class C(JsonClassSerializable):
    
        def __init__(self):
            self.mill = "s"
    
    
    JsonClassSerializable.register(C)
    
    
    class B(JsonClassSerializable):
    
        def __init__(self):
            self.a = 1230
            self.c = C()
    
    
    JsonClassSerializable.register(B)
    
    
    class A(JsonClassSerializable):
    
        def __init__(self):
            self.a = 1
            self.b = {1, 2}
            self.c = B()
    
    JsonClassSerializable.register(A)
    
    A().encode_("test")
    b = A()
    b.decode_("test")
    print(b.a)
    print(b.b)
    print(b.c.a)
    

    编辑

    通过更多研究,我找到了一种无需 SUPERCLASS 注册方法调用即可泛化的方法,使用 元类

    import json
    import collections
    
    REGISTERED_CLASS = {}
    
    class MetaSerializable(type):
    
        def __call__(cls, *args, **kwargs):
            if cls.__name__ not in REGISTERED_CLASS:
                REGISTERED_CLASS[cls.__name__] = cls
            return super(MetaSerializable, cls).__call__(*args, **kwargs)
    
    
    class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):
    
        def default(self, obj):
            if isinstance(obj, collections.Set):
                return dict(_set_object=list(obj))
            if isinstance(obj, JsonClassSerializable):
                jclass = {}
                jclass["name"] = type(obj).__name__
                jclass["dict"] = obj.__dict__
                return dict(_class_object=jclass)
            else:
                return json.JSONEncoder.default(self, obj)
    
        def json_to_class(self, dct):
            if '_set_object' in dct:
                return set(dct['_set_object'])
            elif '_class_object' in dct:
                cclass = dct['_class_object']
                cclass_name = cclass["name"]
                if cclass_name not in REGISTERED_CLASS:
                    raise RuntimeError(
                        "Class {} not registered in JSON Parser"
                        .format(cclass["name"])
                    )
                instance = REGISTERED_CLASS[cclass_name]()
                instance.__dict__ = cclass["dict"]
                return instance
            return dct
    
        def encode_(self, file):
            with open(file, 'w') as outfile:
                json.dump(
                    self.__dict__, outfile,
                    cls=JsonClassSerializable,
                    indent=4,
                    sort_keys=True
                )
    
        def decode_(self, file):
            try:
                with open(file, 'r') as infile:
                    self.__dict__ = json.load(
                        infile,
                        object_hook=self.json_to_class
                    )
            except FileNotFoundError:
                print("Persistence load failed "
                      "'{}' do not exists".format(file)
                      )
    
    
    class C(JsonClassSerializable):
    
        def __init__(self):
            self.mill = "s"
    
    
    class B(JsonClassSerializable):
    
        def __init__(self):
            self.a = 1230
            self.c = C()
    
    
    class A(JsonClassSerializable):
    
        def __init__(self):
            self.a = 1
            self.b = {1, 2}
            self.c = B()
    
    
    A().encode_("test")
    b = A()
    b.decode_("test")
    print(b.a)
    # 1
    print(b.b)
    # {1, 2}
    print(b.c.a)
    # 1230
    print(b.c.c.mill)
    # s
    

    【讨论】:

    • 我喜欢它。这是惊人的实现。如果可以的话,我只有 1 个问题,你为什么在元类中使用 call 方法而不是 new 方法?我问,因为每次创建新实例时都会调用 call 方法,因此代码会尝试为每个新实例注册类。没什么大不了的,正如我写的那样,我喜欢这个实现,它看起来真的很棒:-)
    【解决方案7】:

    JSON 并不是真正用于序列化任意 Python 对象。它非常适合序列化dict 对象,但pickle 模块确实是您通常应该使用的。 pickle 的输出并不是真正的人类可读的,但它应该可以很好地解开。如果您坚持使用 JSON,可以查看 jsonpickle 模块,这是一种有趣的混合方法。

    https://github.com/jsonpickle/jsonpickle

    【讨论】:

    • 我看到的 pickle 的主要问题是它是一种特定于 Python 的格式,而 JSON 是一种独立于平台的格式。如果您正在编写 Web 应用程序或某些移动应用程序的后端,那么 JSON 特别有用。话虽如此,感谢您指出 jsonpickle。
    • @Haroldo_OK jsonpickle 是否仍然导出为 JSON,只是不太可读?
    • 问题不在于序列化,而在于序列化为 JSON。这样做的原因有很多,而对于大多数人来说,pickle 将是一个可怕的工具。
    【解决方案8】:

    这里有两个简单的函数,用于序列化任何不复杂的类,没有像前面解释的那样花哨。

    我将它用于配置类型的东西,因为我可以在不调整代码的情况下向类添加新成员。

    import json
    
    class SimpleClass:
        def __init__(self, a=None, b=None, c=None):
            self.a = a
            self.b = b
            self.c = c
    
    def serialize_json(instance=None, path=None):
        dt = {}
        dt.update(vars(instance))
    
        with open(path, "w") as file:
            json.dump(dt, file)
    
    def deserialize_json(cls=None, path=None):
        def read_json(_path):
            with open(_path, "r") as file:
                return json.load(file)
    
        data = read_json(path)
    
        instance = object.__new__(cls)
    
        for key, value in data.items():
            setattr(instance, key, value)
    
        return instance
    
    # Usage: Create class and serialize under Windows file system.
    write_settings = SimpleClass(a=1, b=2, c=3)
    serialize_json(write_settings, r"c:\temp\test.json")
    
    # Read back and rehydrate.
    read_settings = deserialize_json(SimpleClass, r"c:\temp\test.json")
    
    # results are the same.
    print(vars(write_settings))
    print(vars(read_settings))
    
    # output:
    # {'c': 3, 'b': 2, 'a': 1}
    # {'c': 3, 'b': 2, 'a': 1}
    

    【讨论】:

    • 嘿@GBGOLC,感谢您提供的那段很棒的代码!我尝试使用 json.loads 和转储来实现它,因为没有任何文件在我使用代码的地方。在我的示例中,我正在努力解码 Vehicle 类,它指的是车轮。 Here's 我是如何改变它的。非常感谢您的帮助。
    • 好的,我解决了我自己的问题:因为 decode_ 改变了它传递的实例,所以没有返回值传递给一个新的实例。您的代码很好,只有我调用 decode_ 的方式需要更改。再次感谢伟大的代码!
    【解决方案9】:

    我相信,与其在接受的答案中建议继承,不如使用多态性。否则,您必须有一个大的 if else 语句来自定义每个对象的编码。这意味着为 JSON 创建一个通用的默认编码器:

    def jsonDefEncoder(obj):
       if hasattr(obj, 'jsonEnc'):
          return obj.jsonEnc()
       else: #some default behavior
          return obj.__dict__
    

    然后在要序列化的每个类中都有一个jsonEnc() 函数。例如

    class A(object):
       def __init__(self,lengthInFeet):
          self.lengthInFeet=lengthInFeet
       def jsonEnc(self):
          return {'lengthInMeters': lengthInFeet * 0.3 } # each foot is 0.3 meter
    

    然后你打电话给json.dumps(classInstance,default=jsonDefEncoder)

    【讨论】:

    • 这是正确答案。该类知道如何序列化自己。可惜不能直接把方法名传给json.dumps
    【解决方案10】:

    关于如何开始执行此操作有一些很好的答案。但是有一些事情要记住:

    • 如果实例嵌套在大型数据结构中怎么办?
    • 如果还想要类名怎么办?
    • 如果要反序列化实例怎么办?
    • 如果您使用__slots__ 而不是__dict__ 会怎样?
    • 如果您不想自己动手怎么办?

    json-tricks 是一个库(由我创建并由其他人贡献),它已经能够做到这一点已有一段时间了。例如:

    class MyTestCls:
        def __init__(self, **kwargs):
            for k, v in kwargs.items():
                setattr(self, k, v)
    
    cls_instance = MyTestCls(s='ub', dct={'7': 7})
    
    json = dumps(cls_instance, indent=4)
    instance = loads(json)
    

    您将取回您的实例。这里的 json 看起来像这样:

    {
        "__instance_type__": [
            "json_tricks.test_class",
            "MyTestCls"
        ],
        "attributes": {
            "s": "ub",
            "dct": {
                "7": 7
            }
        }
    }
    

    如果你想自己解决,可以看看json-tricks的来源,以免忘记一些特殊情况(比如__slots__)。

    它还处理其他类型,如 numpy 数组、日期时间、复数;它还允许 cmets。

    【讨论】:

      【解决方案11】:

      这可以通过pydantic 轻松处理,因为它已经内置了这个功能。

      选项1:正常方式

      from pydantic import BaseModel
      
      class testclass(BaseModel):
          value1: str = "a"
          value2: str = "b"
      
      test = testclass()
      
      >>> print(test.json(indent=4))
      {
          "value1": "a",
          "value2": "b"
      }
      

      选项 2:使用 pydantic 的数据类

      import json
      from pydantic.dataclasses import dataclass
      from pydantic.json import pydantic_encoder
      
      @dataclass
      class testclass:
          value1: str = "a"
          value2: str = "b"
      
      test = testclass()
      >>> print(json.dumps(test, indent=4, default=pydantic_encoder))
      {
          "value1": "a",
          "value2": "b"
      }
      

      【讨论】:

        【解决方案12】:

        我一直在我的 Flask 应用程序中使用的一种将 Class 实例序列化为 JSON 响应的方法。

        Github project for reference

        from json import JSONEncoder
        import json
        from typing import List
        
        class ResponseEncoder(JSONEncoder):
            def default(self, o):
                return o.__dict__
        
        class ListResponse:
            def __init__(self, data: List):
                self.data = data
                self.count = len(data)
        
        class A:
            def __init__(self, message: str):
                self.message = message
        
        class B:
            def __init__(self, record: A):
                self.record = record
        
        class C:
            def __init__(self, data: B):
                self.data = data
        

        现在创建一个 A、B、C 的实例,然后进行编码。

        data_a = A('Test Data')
        data_b = B(data_a)
        data_c = C(data_b)
        
        response = ResponseEncoder().encode(data_c)
        json_response = json.loads(response)
        

        输出

        {
            "data": {
                "record": {
                    "message": "Test Data"
                }
            }
        }
        

        对于列表类型的响应

        records = ['One', 'Two', 'Three']
        list_response = ListResponse(records)
        response = ResponseEncoder().encode(list_response)
        json_response = json.loads(response)
        

        输出

        {
            "data": [
                "One",
                "Two",
                "Three"
            ],
            "count": 3
        }
        

        【讨论】:

          【解决方案13】:

          这里还有另一种非常简单和优雅的方法可以应用,它只是将 'dict' 子类化,因为它默认是可序列化的。

          from json import dumps
          
          class Response(dict):
              def __init__(self, status_code, body):
                  super().__init__(
                      status_code = status_code,
                      body = body
                  )
          
          r = Response()
          dumps(r)
          

          【讨论】:

          • 这会导致一些奇怪的结果,这可能没问题,只是令人困惑:r = Response(200, 'blah'); r['a'] = 1; r.b = 2 导致r == {'status_code': 200, 'body': 'blah', 'a': 1}r.__dict__ == {'b': 2}。我很想知道dict 实际存储它的键和值的位置。
          【解决方案14】:

          使用任意、可扩展的对象,然后将其序列化为 JSON:

          import json
          
          class Object(object):
              pass
          
          response = Object()
          response.debug = []
          response.result = Object()
          
          # Any manipulations with the object:
          response.debug.append("Debug string here")
          response.result.body = "404 Not Found"
          response.result.code = 404
          
          # Proper JSON output, with nice formatting:
          print(json.dumps(response, indent=4, default=lambda x: x.__dict__))
          

          【讨论】:

            【解决方案15】:

            您可以使用 Jsonic 将几乎所有内容序列化为 JSON:

            https://github.com/OrrBin/Jsonic

            例子:

            class TestClass:
            def __init__(self):
                self.x = 1
                self.y = 2
            
            instance = TestClass()
            s = serialize(instance): # instance s set to: {"x":1, "y":2}
            d = deserialize(s) # d is a new class instance of TestClass
            

            Jsonic 有一些不错的功能,例如声明类属性瞬态和类型安全反序列化。

            (答案晚了几年,但我认为它可能对其他人有所帮助)

            【讨论】:

              【解决方案16】:

              我为此做了一个函数,效果很好:

              def serialize(x,*args,**kwargs):
                  kwargs.setdefault('default',lambda x:getattr(x,'__dict__',dict((k,getattr(x,k) if not callable(getattr(x,k)) else repr(getattr(x,k))) for k in dir(x) if not (k.startswith('__') or isinstance(getattr(x,k),x.__class__)))))
                  return json.dumps(x,*args,**kwargs)
              

              【讨论】:

                【解决方案17】:

                你可以试试objprint,这是一个用于打印Python对象的轻量级库,它支持json输出。

                pip install objprint
                
                from objprint import objjson
                t = testclass()
                json_obj = objjson(t)
                print(json.dumps(json_obj))
                

                objjson 基本上将任意对象转换为 jsonifiable 对象,如果它不是 dict、list 等内置类型,则使用特殊键 .type 表示它的原始 Python 类型。

                如果您只是想打印它,您可以使用op,它通常用于以人类可读的格式打印对象。

                from objprint import op
                t = testclass()
                op(t, format="json", indent=2)
                
                # If you want to dump to a file
                with open("my_obj.json", "w") as f:
                    # This is the same usage as print
                    op(t, format="json", file=f)
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多