如何让Python jsonpickle递归工作?

时间:2013-01-15 00:58:35

标签: python json jsonpickle

我无法让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)

球有“颜色”,盒子有“内容”。如果我有一个[本机]球阵列,那么jsonpickle可以正常工作。如果我有一盒[native] int,那么jsonpickle就可以了。

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

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

我该如何解决这个问题?

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

感谢任何输入!

3 个答案:

答案 0 :(得分:4)

首先,您为什么要首先创建自定义处理程序?您尝试执行与默认处理程序已完成相同的操作。移除这两条register行并使用encode和所有这些对象调用unpicklable=False,您将获得相同的结果 - 除了它将按照您想要的方式工作充满了球,而不是失败。

如果您查看教程,API,测试用例和示例,他们永远不会创建自定义处理程序来模拟这样的集合。 (例如,查看测试套件中的Node / Document / Section类(samples.pydocument_test.py)。)所以,我认为你正试图做一些你不应该做的事情,并不打算做。

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

嗯,那个很容易。你这样做是错的。根据{{​​3}}的文档,您应该:

  

将obj变成json友好的形式。

所以,鉴于此:

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

您有效地承诺obj.contents采用JSON友好形式。但事实并非如此;它是listBall个对象。

那么,什么是正确的答案?好吧,你可以压缩内容中的每个元素,就像你被夷为平地一样。您认为必须有一些简单的方法可以做到这一点,但老实说,我没有在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的{​​{1}}参数。

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

我认为你可以回调酸洗环境继续酸洗。

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

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

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

我现在正在对此进行测试,并且_depth似乎按预期进行了维护。