使用常规编码器使对象可JSON序列化
使用常规编码器使对象可JSON序列化
将自定义的不可序列化对象进行JSON序列化的常规方法是子类化json.JSONEncoder
,然后将自定义编码器传递给json.dumps()
。
通常是这样的:
class CustomEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Foo): return obj.to_json() return json.JSONEncoder.default(self, obj) print(json.dumps(obj, cls=CustomEncoder))
我想要做的是使用默认编码器使某个对象可序列化。我搜索了一下,但没有找到任何相关信息。
我的想法是编码器会查看某个字段来确定JSON编码,类似于__str__
。也许是__json__
字段。
在Python中是否有类似的机制?
我想使我正在开发的模块中的一个类对每个使用该包的人都可序列化,而无需担心实现他们自己的[微不足道的]自定义编码器。
在这段代码中,我们定义了一个名为MyClass的类,它包含了一个名为_jsonSupport的方法,该方法用于支持将对象转化为JSON格式。该方法内部定义了两个函数:default和objectHook,分别用于编码和解码JSON对象。在default函数中,我们将对象转化为一个字典,其中包含了对象的类型和名称。在objectHook函数中,我们根据字典中的'type'键值来判断是否为MyClass类型的对象,并根据名称创建相应的对象。通过将这两个函数赋值给json模块的JSONEncoder.default和JSONDecoder的object_hook属性,我们就实现了将MyClass对象转化为JSON格式以及将JSON格式转化为MyClass对象的功能。
这种方法的一个明显限制是,目前的实现方式不能同时支持多个类,因为它们会互相干扰彼此的JSON支持代码。即使在这种情况下它可以正常工作,也需要在每个类中重复并放置类似的支持代码。然而,可能可以修复这两个问题。
问题的出现原因是json模块的默认编码器无法序列化自定义对象。解决方法是通过monkey-patching来更改默认编码器的default方法,使其能够自动检查对象是否有特殊的to_json方法,并使用该方法对对象进行编码。
具体实现的代码如下:
""" Module that monkey-patches json module when it's imported so JSONEncoder.default() automatically checks for a special "to_json()" method and uses it to encode the object if found. """ from json import JSONEncoder def _default(self, obj): return getattr(obj.__class__, "to_json", _default.default)(obj) _default.default = JSONEncoder.default # Save unmodified default. JSONEncoder.default = _default # Replace it.
使用这个模块的方法很简单,只需要导入即可。例如:
import json import make_json_serializable # apply monkey-patch class Foo(object): def __init__(self, name): self.name = name def to_json(self): # New special method. """ Convert to JSON format string representation. """ return '{"name": "%s"}' % self.name foo = Foo('sazpaz') print(json.dumps(foo)) # -> "{\"name\": \"sazpaz\"}"
通过以上方法,我们可以将自定义对象序列化为JSON格式的字符串。为了保留对象类型信息,可以在特殊方法中添加类型信息。例如:
def to_json(self): return ('{"type": "%s", "name": "%s"}' % (self.__class__.__name__, self.name))
这样生成的JSON字符串会包含类名信息。
另外,还有一种更好的方法可以自动序列化大部分Python对象,包括用户定义的类实例,而不需要添加特殊方法。该方法基于pickle模块,具体代码如下:
""" Module that imports the json module and monkey-patches it so JSONEncoder.default() automatically pickles any Python objects encountered that aren't standard JSON data types. """ from json import JSONEncoder import pickle def _default(self, obj): return {'_python_object': pickle.dumps(obj)} JSONEncoder.default = _default # Replace with the above.
使用这种方法,遇到非标准JSON数据类型的Python对象时,会自动进行pickle处理。为了反序列化,可以在json.loads()函数调用时提供一个自定义的object_hook函数作为参数。例如:
def as_python_object(dct): try: return pickle.loads(dct['_python_object']) except KeyError: return dct pyobj = json.loads(json_str, object_hook=as_python_object)
如果需要在多个地方进行这样的反序列化操作,可以定义一个包装函数,自动提供额外的关键字参数。例如:
import functools json_pkloads = functools.partial(json.loads, object_hook=as_python_object) pyobj = json_pkloads(json_str)
以上方法可以很方便地实现Python对象的序列化和反序列化。
最后提到了在Python 3中的兼容性问题,因为json.dumps()返回的是一个bytes对象,而JSONEncoder无法处理。解决方法是将pickle.dumps()返回的值进行latin1编码和解码。具体代码如下:
from decimal import Decimal class PythonObjectEncoder(json.JSONEncoder): def default(self, obj): return {'_python_object': pickle.dumps(obj).decode('latin1')} def as_python_object(dct): try: return pickle.loads(dct['_python_object'].encode('latin1')) except KeyError: return dct data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'}, Foo('Bar'), Decimal('3.141592653589793238462643383279502884197169')] j = json.dumps(data, cls=PythonObjectEncoder, indent=4) data2 = json.loads(j, object_hook=as_python_object) assert data == data2 # both should be same
这样就可以在Python 3中正常使用了。
以上就是解决使对象能够被JSON序列化并用常规编码器进行编码的问题的原因和解决方法。
问题的出现原因是想要使用普通编码器(regular encoder)将对象转换为JSON字符串时遇到了一些问题。解决方法是创建一个可序列化的类,并继承自dict类,然后通过添加一个虚拟的键值对来解决编码器无法序列化的问题。
在给出的代码中,定义了一个名为Serializable的类,该类继承自dict类。在该类的构造函数中,通过添加一个名为"dummy"的键值对,来解决编码器无法序列化的问题。该类还定义了_myattrs方法来获取对象的属性列表,_repr方法用于将属性的值转换为可序列化的类型,以及keys、values和items方法用于返回属性的键、值和键值对。
为了使自定义的类能够使用普通编码器进行序列化,可以继承自Serializable类,并定义自己的属性和方法。在给出的代码中,定义了一个名为MySerializableClass的类,该类继承自Serializable类,并定义了两个属性attr_1和attr_2,以及一个方法my_function。通过创建一个MySerializableClass的对象obj,并使用print和json.dumps方法来输出该对象,可以看到对象被成功地序列化为JSON字符串。
关于代码中的"# hack to fix _json.so make_encoder serialize properly
"的问题,原作者表示这是一个解决方法,具体实现原理已经遗忘。根据作者的记忆,这个问题是由于json.dumps方法使用了名为"_json.so"的本地实现,当无法使用该实现时,会回退到Python实现(可能较慢)。而"_json.so"在没有任何值的情况下不会序列化类。因此,通过添加一个虚拟的键值对,使得编码器调用items等方法,从而解决了这个问题。
至于第二个问题,解决方法可能涉及到修改或删除__repr__方法,或者在序列化时检查嵌套值是否为dict类型。由于原作者已经很久没有使用Python,具体的解决方法需要重新查看json.dumps方法的实现。
通过继承dict类并添加虚拟键值对的方式,可以使对象能够使用普通编码器进行序列化。尽管具体的解决方法可能需要根据实际情况进行调整,但这种方式为解决对象无法JSON序列化的问题提供了一种思路。