将类实例序列化为JSON

fer*_*han 160 python serialization json pickle

我正在尝试创建类实例的JSON字符串表示并且有困难.假设这个类是这样构建的:

class testclass:
    value1 = "a"
    value2 = "b"
Run Code Online (Sandbox Code Playgroud)

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

t = testclass()
json.dumps(t)
Run Code Online (Sandbox Code Playgroud)

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

TypeError: <__main__.testclass object at 0x000000000227A400> is not JSON serializable
Run Code Online (Sandbox Code Playgroud)

我也尝试过使用pickle模块:

t = testclass()
print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))
Run Code Online (Sandbox Code Playgroud)

它提供类实例信息,但不提供类实例的序列化内容.

b'\x80\x03c__main__\ntestclass\nq\x00)\x81q\x01}q\x02b.'
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

ste*_*eha 214

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

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

一个简单的解决方案是调用该实例json.dumps().__dict__成员.这是一个标准的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}
Run Code Online (Sandbox Code Playgroud)

本博文中讨论了上述方法:

    使用__dict__将任意Python对象序列化为JSON

  • 那是因为你的类没有`.__ init __()`方法函数,所以你的类实例有一个空字典.换句话说,`{}`是示例代码的正确结果. (6认同)
  • 我试过这个.调用json.dumps(t .__ dict__)的最终结果只是{}. (3认同)
  • 谢谢.这样就可以了.我添加了一个简单的__init__没有参数,现在调用json.dumps(t .__ dict__)返回格式为:{"value2":"345","value1":"123"}的正确数据我看过像之前,我不确定我是否需要为成员提供自定义序列化程序,需要__init__没有明确提及或者我错过了它.谢谢. (3认同)
  • 这适用于单个类,但不适用于相关类的对象 (3认同)
  • @NwawelAIroume:是的。如果您有一个对象,例如在一个列表中包含多个对象,则错误仍然是“不是JSON可序列化的” (2认同)
  • 有人可以为从 JSONEncoder 继承然后实现 JSONEncoder.default() 函数的方法提供一个很好的参考吗? (2认同)

Bro*_*oli 51

有一种方法对我很有用,你可以尝试:

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__
Run Code Online (Sandbox Code Playgroud)

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

最后的电话看起来像:

json.dumps(myObj, default=serialize)
Run Code Online (Sandbox Code Playgroud)

当您序列化集合并且不希望__dict__为每个对象显式调用时,它尤其有用.这是为您自动完成的.

到目前为止,对我来说工作非常好,期待你的想法.


cod*_*n48 37

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

json.dumps(obj, default=lambda x: x.__dict__)
Run Code Online (Sandbox Code Playgroud)

说明:

形成文档(2.7,3.6):

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

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

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

我首先从@ phihag的答案中了解到这一点.发现它是最简单,最干净的工作方式.

  • 这对我有用,但由于datetime.date成员我稍微改了一下:`default = lambda x:getattr(x,'__ dict__',str(x))` (5认同)

Spi*_*ail 22

我只是做:

data=json.dumps(myobject.__dict__)
Run Code Online (Sandbox Code Playgroud)

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

它可以很好地工作的是从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)
Run Code Online (Sandbox Code Playgroud)

  • 只要该对象不是由其他对象组成的,那也可以。 (3认同)

gie*_*s0r 14

使用jsonpickle

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)
Run Code Online (Sandbox Code Playgroud)


Bre*_*ood 7

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

https://github.com/jsonpickle/jsonpickle

  • 我看到 pickle 的主要问题是它是一种特定于 Python 的格式,而 JSON 是一种与平台无关的格式。如果您正在为某些移动应用程序编写 Web 应用程序或后端,则 JSON 特别有用。话虽如此,感谢您指出 jsonpickle。 (11认同)

Dav*_*erg 7

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)
Run Code Online (Sandbox Code Playgroud)

编辑

通过更多的研究,我找到了一种无需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
Run Code Online (Sandbox Code Playgroud)


Kur*_*ine 6

这可以使用pydantic轻松处理,因为它已经内置了此功能。

方案一:正常方式

from pydantic import BaseModel

class testclass(BaseModel):
    value1: str = "a"
    value2: str = "b"

test = testclass()

>>> print(test.json(indent=4))
{
    "value1": "a",
    "value2": "b"
}
Run Code Online (Sandbox Code Playgroud)

选项 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"
}
Run Code Online (Sandbox Code Playgroud)


hwa*_*wat 5

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

def jsonDefEncoder(obj):
   if hasattr(obj, 'jsonEnc'):
      return obj.jsonEnc()
   else: #some default behavior
      return obj.__dict__
Run Code Online (Sandbox Code Playgroud)

然后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
Run Code Online (Sandbox Code Playgroud)

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


GBG*_*OLC 5

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

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

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}
Run Code Online (Sandbox Code Playgroud)