将类实例序列化为JSON

我试图创建一个类实例的JSON字符串表示,有困难。假设这个类是这样构建的:

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

对json的调用。转储是这样的:

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.'

我做错了什么?

406172 次浏览

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

上述方法在这篇博文中进行了讨论:

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

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

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

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

JSON实际上并不用于序列化任意的Python对象。它非常适合序列化dict对象,但在一般情况下,pickle模块才是真正应该使用的模块。pickle的输出不是真正的人类可读的,但它应该可以解pickle。如果你坚持使用JSON,你可以检查jsonpickle模块,这是一种有趣的混合方法。

https://github.com/jsonpickle/jsonpickle

我只会:

data=json.dumps(myobject.__dict__)

这不是完整的答案,如果你有某种复杂的对象类,你肯定不会得到所有的东西。然而,我在一些简单的对象中使用这种方法。

其中一个工作得很好的是你从OptionParser模块中获得的“options”类。 下面是它和JSON请求本身。< / p >
  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)

有一种方法非常适合我,你可以试试:

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__时,它特别好。这里是自动完成的。

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

我认为,与其像公认的答案中建议的那样继承,不如使用多态。否则你必须有一个大的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)

使用jsonpickle

import jsonpickle


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

你可以在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的回答在这里中了解到这一点。发现这是最简单、最干净的工作方式。

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

  • 如果实例嵌套在大型数据结构中怎么办?
  • 如果还想要类名呢?
  • 如果要反序列化实例该怎么办?
  • 如果你使用__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数组,datetimes,复数;它还允许注释。

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

我将此用于配置类型的东西,因为我可以向类添加新成员而无需进行代码调整。

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}

Python3.x

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

然而,这是一个编解码器。

再做一点工作,你可以用其他方式构造你的类。 我假设有一个默认构造函数来实例它,然后更新类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)

编辑

通过更多的研究,我发现了一种不需要调用超类寄存器方法的泛化方法,使用元类

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

你可以使用Jsonic for将几乎任何东西序列化为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有一些很好的特性,比如声明类属性瞬态和类型安全反序列化。

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

我一直在我的Flask应用程序中使用的一种方法,将类实例序列化为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
}

还有另一种非常简单和优雅的方法可以应用在这里,那就是只子类'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)

使用任意的可扩展对象,然后将其序列化为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__))

这可以很容易地用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"
}

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

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)

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

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

objjson基本上将任意对象转换为jsoniizable对象,如果它不是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)