pythonjsonjsonpickle

How can I get Python jsonpickle to work recursively?


I'm having trouble getting Python's jsonpickle 0.4.0 to "recurse" in to custom objects that contain custom objects. Here's sample code that shows my problem.

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)

Balls have "color", Boxes have "contents". If I have a [native] array of Balls, then jsonpickle works. If I have a Box of [native] ints, then jsonpickle works.

But if I have a Box of Balls, jsonpickle bombs with "Ball object is not JSON serializable".

From the stacktrace, I have the hunch that the encoder is leaving jsonpickle and going off to some other JSON library... that apparently doesn't know that I've registered the BallHandler.

How can I fix this up?

By the way, my sample is NOT expressly using any part of Django, but I will be needing this to work in a Django app.

THANKS IN ADVANCE FOR ANY INPUT!


Solution

  • I think you can call back to the pickling context to continue the pickling.

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

    This seems to be similar to how the built in _list_recurse() function handles this case in pickler.py:44, as flatten() just calls self._flatten (after optionally resetting the state variables).

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

    I'm just testing on this now, and the _depth seems to be maintained as expected.