gpt4 book ai didi

python - 使用常规编码器使对象 JSON 可序列化

转载 作者:太空宇宙 更新时间:2023-11-03 20:11:48 25 4
gpt4 key购买 nike

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__ field 。python中有类似的东西吗?

我想让我正在制作的模块的一个类对于使用该包的每个人都可以 JSON 序列化,而无需他们担心实现自己的[琐碎]自定义编码器。

最佳答案

正如我在对您的问题的评论中所说,在查看 json 模块的源代码后,它似乎不适合做您想做的事情。然而,这个目标可以通过所谓的monkey-patching来实现。(参见问题 What is a monkey patch? )。这可以在包的 __init__.py 初始化脚本中完成,并且会影响所有后续的 json 模块序列化,因为模块通常只加载一次,并且结果缓存在 中sys.modules.

该补丁更改了默认 json 编码器的 default 方法 - 默认的 default()

为了简单起见,这里有一个作为独立模块实现的示例:

模块:make_json_serialized.py

""" 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\"}"

为了保留对象类型信息,特殊方法还可以将其包含在返回的字符串中:

        return ('{"type": "%s", "name": "%s"}' %
(self.__class__.__name__, self.name))

这会生成以下 JSON,其中现在包含类名称:

"{\"type\": \"Foo\", \"name\": \"sazpaz\"}"

魔法就在这里

比替换 default() 寻找特殊命名的方法更好的是,它能够自动序列化大多数 Python 对象,包括用户-定义类实例,无需添加特殊方法。在研究了多种替代方案后,以下内容基于 answer @Raymond Hettinger 提出的另一个问题 - 使用 pickle 模块,似乎最接近我的理想:

模块:make_json_serialized2.py

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

当然,所有东西都不能被腌制——例如扩展类型。然而,有一些定义的方法可以通过 pickle 协议(protocol)通过编写特殊方法来处理它们 - 类似于您建议的和我之前描述的 - 但对于很少数量的情况来说,这样做可能是必要的。

反序列化

无论如何,使用 pickle 协议(protocol)也意味着通过在任何 json.loads() 上提供自定义 object_hook 函数参数来重建原始 Python 对象将相当容易使用传入的字典中的任何 '_python_object' 键的调用(无论何时)。像这样的东西:

def as_python_object(dct):
try:
return pickle.loads(str(dct['_python_object']))
except KeyError:
return dct

pyobj = json.loads(json_str, object_hook=as_python_object)

如果必须在很多地方执行此操作,则可能值得定义一个自动提供额外关键字参数的包装函数:

json_pkloads = functools.partial(json.loads, object_hook=as_python_object)

pyobj = json_pkloads(json_str)

当然,也可以将其猴子修补到 json 模块中,使该函数成为默认的 object_hook (而不是 None )。

我从 answer 中得到了使用 pickle 的想法通过 Raymond Hettinger另一个 JSON 序列化问题,我认为该问题非常可信,并且是官方来源(如 Python 核心开发人员)。

Python 3 的可移植性

上面的代码无法像 Python 3 中所示那样工作,因为 json.dumps() 返回 bytes 对象,而 JSONEncoder 可以' t handle 。然而,该方法仍然有效。解决此问题的一个简单方法是 latin1“解码”从 pickle.dumps() 返回的值,然后从 latin1 对其进行“编码” > 然后将其传递给 as_python_object() 函数中的 pickle.loads() 。这是有效的,因为任意二进制字符串都是有效的 latin1 ,它总是可以解码为 Unicode,然后再次编码回原始字符串(如 this answerSven Marnach 中指出的)。

(尽管以下代码在 Python 2 中工作正常,但它所做的 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


class Foo(object): # Some user-defined class.
def __init__(self, name):
self.name = name

def __eq__(self, other):
if type(other) is type(self): # Instances of same class?
return self.name == other.name
return NotImplemented

__hash__ = None


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 - 使用常规编码器使对象 JSON 可序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58672677/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com