将类实例序列化为JSON


185

我正在尝试创建类实例的JSON字符串表示形式并且遇到困难。假设该类的构建如下:

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

像这样对json.dumps进行调用:

t = testclass()
json.dumps(t)

失败了,并告诉我testclass不可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.'

我究竟做错了什么?



30
用一条线,s = json.dumps(obj, default=lambda x: x.__dict__)以序列化对象的实例变量(self.value1self.value2,...)。这是最简单,最直接的方法。它将序列化嵌套的对象结构。default当任何给定对象不可直接序列化时,将调用该函数。您也可以在下面查看我的答案。我发现流行的答案不必要地复杂,很久以前就可能是正确的。
codeman48

1
testclass没有__init__()方法,因此所有实例将共享在class语句中定义的相同的两个类属性(value1value2)。您了解类和一个实例之间的区别吗?
martineau

1
这个github.com/jsonpickle/jsonpickle有一个python库(请注意,因为答案在线程中太低了,将无法访问。)
祝愿

Answers:


237

基本问题是,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}

在此博客文章中讨论了上述方法:

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


3
我试过了 调用json.dumps(t .__ dict__)的最终结果仅为{}。
ferhan

6
那是因为您的类没有.__init__()方法函数,所以您的类实例有一个空的字典。换句话说,这{}是示例代码的正确结果。
steveha

3
谢谢。这可以解决问题。我添加了一个没有参数的简单初始化,现在调用json.dumps(t .__ dict__)以以下格式返回正确的数据:{“ value2”:“ 345”,“ value1”:“ 123”}我见过类似在此之前,不确定我是否需要为成员使用自定义序列化程序,是否没有明确提到init还是错过了它。谢谢。
ferhan

3
这项工作适用于单个课程,但不适用于相关课程
objets

2
@NwawelAIroume:是的。如果您有一个对象,例如在一个列表中包含多个对象,则错误仍然是is not JSON serializable
gies0r

57

您可以尝试一种对我有用的方法:

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

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

如此令人惊讶的是,如此流行的语言没有人可以用jsoniny对象。必须是因为它不是静态类型的。
TheRennen

48

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

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的答案中学到了这一点。发现它是最简单,最干净的方法。


6
这对我有用,但是由于datetime.date成员的缘故,我做了些微更改:default=lambda x: getattr(x, '__dict__', str(x))
Dakota Hawkins

@Dakota不错的解决方法;datetime.date是C实现,因此没有__dict__属性。恕我直言,为了均匀起见,datetime.date应该拥有它……
codeman48 '18

22

我只是做:

data=json.dumps(myobject.__dict__)

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

在OptionParser模块中获得的“ options”类确实非常有效。这里是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,则可能要删除self。
SpiRail

3
只要该对象不是由其他对象组成的,那也可以。
Haroldo_OK 2014年


5

JSON并非真正意在序列化任意Python对象。这对于序列化dict对象pickle非常有用,但是实际上通常应该使用该模块。的输出pickle不是真正的人类可读的,但是应该可以使它顺畅运行。如果您坚持使用JSON,则可以签出jsonpickle模块,这是一种有趣的混合方法。

https://github.com/jsonpickle/jsonpickle


9
我在pickle上看到的主要问题是它是特定于Python的格式,而JSON是与平台无关的格式。如果您要编写Web应用程序或某些移动应用程序的后端,则JSON特别有用。话虽如此,感谢您指出jsonpickle。
Haroldo_OK 2014年

@Haroldo_OK jsonpickle仍然不导出到JSON,只是不是很容易理解?
Caelum 2015年

4

这是两个简单的函数,用于对任何不复杂的类进行序列化,没有任何花哨的地方。

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

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}

3

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

  • 如果实例嵌套在大型数据结构中怎么办?
  • 如果还想要班级名称怎么办?
  • 如果要反序列化实例怎么办?
  • 如果您正在使用该怎么办 __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数组,日期时间,复数;它还允许发表评论。


3

Python3.x

我所能达到的最好的方法就是这个。
注意,此代码也对待set()。
这种方法是通用的,只需要扩展类(在第二个示例中)。
请注意,我只是在处理文件,但是很容易根据自己的喜好修改行为。

但是,这是CoDec。

通过更多的工作,您可以用其他方式构造您的课程。我假定使用默认的构造函数来实例化它,然后更新类dict。

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

2

我相信与其采用公认的答案所建议的继​​承,不如使用多态性更好。否则,必须有一个很大的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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.