【问题标题】:How can I get Python jsonpickle to work recursively?如何让 Python jsonpickle 递归工作?
【发布时间】:2013-01-15 00:58:35
【问题描述】:

我无法让 Python 的 jsonpickle 0.4.0“递归”到包含自定义对象的自定义对象中。这是显示我的问题的示例代码。

import jsonpickle
import jsonpickle.handlers

class Ball(object):
    def __init__(self, color):
        self.color = color

class Box(object):
    def __init__(self, *args):
        self.contents = args

class BallHandler(jsonpickle.handlers.BaseHandler):    
    def flatten(self, obj, data):
        data['color'] = obj.color
        return data

class BoxHandler(jsonpickle.handlers.BaseHandler):    
    def flatten(self, obj, data):
        data['contents'] = obj.contents
        return data

jsonpickle.handlers.registry.register(Ball, BallHandler)
jsonpickle.handlers.registry.register(Box, BoxHandler)

# works OK -- correctly prints: {"color": "white"}
white_ball = Ball('white')
print jsonpickle.encode(white_ball, unpicklable=False)

# works OK -- correctly prints: [{"color": "white"}, {"color": "green"}]
green_ball = Ball('green')
balls = [white_ball, green_ball]
print jsonpickle.encode(balls, unpicklable=False)

# works OK -- correctly prints: {"contents": [1, 2, 3, 4]}
box_1 = Box(1, 2, 3, 4)
print jsonpickle.encode(box_1, unpicklable=False)

# dies with "Ball object is not JSON serializable"
box_2 = Box(white_ball, green_ball)
print jsonpickle.encode(box_2, unpicklable=False)

球有“颜色”,盒子有“内容”。如果我有一个 [native] Balls 数组,那么 jsonpickle 可以工作。如果我有一盒 [native] ints,那么 jsonpickle 可以工作。

但如果我有一盒球,jsonpickle"Ball object is not JSON serializable" 炸弹。

从堆栈跟踪中,我预感到编码器将离开 jsonpickle 并转到其他一些 JSON 库...显然不知道我已经注册了 BallHandler。

我该如何解决这个问题?

顺便说一句,我的示例没有明确使用 Django 的任何部分,但我需要它才能在 Django 应用程序中工作。

提前感谢您的任何意见!

【问题讨论】:

    标签: python json jsonpickle


    【解决方案1】:

    我认为你可以回调酸洗上下文继续酸洗。

    class BoxHandler(jsonpickle.handlers.BaseHandler):
    def flatten(self, obj, data):
        return [self.context.flatten(x,reset=False) for x in obj.contents]
    

    这似乎类似于 pickler.py:44 中内置的 _list_recurse() 函数处理这种情况的方式,因为 flatten() 只是调用 self._flatten(在可选地重置状态变量之后)。

    def _list_recurse(self, obj): return [self._flatten(v) for v in obj]

    我现在只是对此进行测试,_depth 似乎按预期保持。

    【讨论】:

    • 这是我一直在寻找的答案。谢谢!
    【解决方案2】:

    首先,您为什么要创建自定义处理程序?您正在尝试做与默认处理程序已经做的完全相同的事情。删除这两条register 行并调用encodeunpicklable=False 与所有这些对象,你会得到相同的结果——除了它会完全按照你想要的方式处理装满球的盒子,而不是失败。

    如果您浏览本教程、API、测试用例和示例,它们永远不会创建自定义处理程序来模拟这样的集合。 (例如,看看测试套件中的Node/Document/Section 类(samples.pydocument_test.py)。)所以,我认为你正在尝试做一些你曾经做过的事情'不希望这样做,也不打算这样做。

    但是,让我们看看您的实际问题:为什么它不起作用?

    嗯,这很容易。你这样做是错的。根据BaseHandler.flatten 的文档,您应该:

    将 obj 扁平化为对 json 友好的形式。

    所以,鉴于此:

    class BoxHandler(jsonpickle.handlers.BaseHandler):    
        def flatten(self, obj, data):
            data['contents'] = obj.contents
            return data
    

    您有效地承诺obj.contents 是JSON 友好的形式。但事实并非如此;这是 list 中的 Ball 个对象。

    那么,正确答案是什么?好吧,您可以以与展平相同的方式展平内容中的每个元素。您认为必须有一些简单的方法可以做到这一点,但老实说,我在 API、文档、示例或单元测试中看不到任何内容,所以我想没有,所以您必须这样做它手动。大概是这样的(未经测试):

    class BoxHandler(jsonpickle.handlers.BaseHandler):    
        def flatten(self, obj, data):
            p = jsonpickle.Pickler()
            data['contents'] = [p.flatten(elem) for elem in obj.contents]
            return data
    

    但是……因为你没有得到相同的 Pickler 来腌制你——而且我看不出你有任何方法——这很可能会违反 maxdepthunpicklable 参数encode.

    所以,也许没有正确的方法来做到这一点。

    【讨论】:

    • 对。 data['contents'] 的所有元素都是 JSON 友好的......因为我已经注册了一个处理程序,因此 JSON 知道如何对它们进行编码!
    • 不,这不会使 JSON 友好。 JSON 不知道如何对它们进行编码; jsonpickle 确实如此。那不是一回事。即使措辞有点模棱两可,堆栈跟踪也清楚地表明它想要真正的 JSON 友好,而不仅仅是进一步扁平化友好。 (如果您考虑一下,这是有道理的。如果您想实现某种与通常的 list/set/dict 类型(例如二叉树)不同的集合,您显然需要把它压扁,否则它不知道你实际上给了它什么。)
    • 至于“为什么我首先要创建自定义处理程序”:嗯...这是示例代码。证明问题。这不是我真正的代码。我要编码的“真实”对象是复杂的 Django 模型。但我只需要对需要发送到 Web 应用程序的对象部分进行编码。所以我需要控制 JSON 编码。
    【解决方案3】:

    对我来说看起来像是一个错误,也是一个原则。如果jsonpickle 是关于向json 添加自定义对象处理,它应该集成到后者中,而不是尝试为其“预处理”内容。要求用户以任何方式自己处理此问题的现状as abarnert said 正在将责任归咎于他人的门 IMO。

    如果我是你,我会自己解决这个问题,或者让我的对象对 JSON 友好——例如使它们看起来像原生 Python 数据结构(JSON 是其替代表示)。当然,更简单的方法是避免这种结构。

    【讨论】:

    • 我不介意“预处理”步骤。但我认为框架没有做递归很奇怪。我的意思是,毕竟:1)对包含的对象进行 JSON 编码是错误的(因为它会导致双重编码)。 2)而且我不必“预处理”我包含的项目,如整数。我想我可以解决预展平我的“包含的东西”....但这对我来说似乎很奇怪。毕竟,在 Box.flatten() 之后的人仍在对整数进行 JSON 编码!为什么它也不能做我的球?
    • 这就是我要说的。进行递归本质上是第二次重做json 的大部分工作——这可能就是他们没有实现这一点的原因。
    • 如果您阅读文档(以及我编辑的答案)并查看示例和测试,我很确定 OP 误解了 jsonpickle 是什么以及它应该如何使用,并且是因此试图做错事。唯一的错误是实现与默认值完全相同的处理程序比应该实现的处理程序更难 - 但是,由于默认值已经与默认值完全相同,所以它不是那么大的错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-19
    • 2016-10-05
    • 1970-01-01
    • 2014-04-17
    • 2019-06-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多