236

我正在尝试创建一个类实例的 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.'

我究竟做错了什么?

4

17 回答 17

290

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

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

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

s = json.dumps(vars(foo)) # s set to: {"x":1, "y":2}
于 2012-04-20T19:09:39.850 回答
79

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

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__为每个对象显式调用时,它特别好。在这里它会自动为您完成。

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

于 2016-12-17T16:25:13.633 回答
75

您可以default在函数中指定命名参数json.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在这里的回答中了解到这一点。发现它是完成这项工作的最简单和最干净的方法。

于 2017-06-27T10:21:37.983 回答
27

使用jsonpickle

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)
于 2017-03-05T17:57:41.643 回答
25

我只是做:

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)
于 2013-07-10T14:36:02.750 回答
8

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
于 2018-08-19T04:34:59.917 回答
6

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

https://github.com/jsonpickle/jsonpickle

于 2012-04-20T19:13:39.493 回答
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}
于 2017-10-10T17:23:07.090 回答
4

我相信,与其在接受的答案中建议继承,不如使用多态性。否则,您必须有一个大的 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)

于 2017-01-04T21:33:38.613 回答
2

关于如何开始这样做有一些很好的答案。但是有一些事情要记住:

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

于 2017-09-19T18:11:12.190 回答
2

这可以使用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"
}
于 2021-02-04T13:08:54.723 回答
1

您可以使用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有一些不错的特性,比如声明类属性瞬态和类型安全反序列化。

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

于 2020-10-11T08:24:20.143 回答
1

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

Github项目供参考

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
}
于 2020-11-19T09:31:14.540 回答
1

还有另一种非常简单和优雅的方法可以在这里应用,它只是子类'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)
于 2021-01-23T02:14:59.637 回答
1

使用任意、可扩展的对象,然后将其序列化为 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__))
于 2021-02-03T11:40:56.157 回答
0

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

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)
于 2021-03-29T18:13:01.897 回答
0

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

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

objjson基本上将任意对象转换为 jsonifiable 对象,.type如果它不是 dict、list 等内置类型,则它具有原始 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)
于 2021-08-28T21:50:40.763 回答