As part of my recent explorations, I’ve been playing a lot with Python iterators/generators. The key efficiency of iterators is that when working with lengthy list-like objects, you need only create the part that’s being looked at. It’s just-in-time objects.
If you attempt to JSON serialize an object with an iterator/generator object in it, the json module throws a cog: it doesn’t know how to serialize these types of objects. The json module is extensible and the documentation makes a suggestion how to do this:
class IterEncoder(json.JSONEncoder):
def default(self, o):
try:
iterable = iter(o)
except TypeError:
pass
else:
return list(iterable)
return JSONEncoder.default(self, o)
print json.dumps(xrange(4), cls = IterEncoder)
This seems somewhat ugly to me. In particular, lots of objects can be wrapped by the iter function that don’t need to be, plus lots of objects will cause that TypeError to be thrown which seems to be rather a bit of waste. Here’s the solution I came up with:
class IterEncoder(json.JSONEncoder):
def default(self, o):
try:
return json.JSONEncoder.default(self, o)
except TypeError, x:
try:
return list(o)
except:
return x
This tries to encode the object the normal way. Only if that doesn’t work do we try to turn the object into a list. If that’s not convertible (i.e. the list object constructor fails) we go back and throw the original exception provided by JSONEncoder – we’ve really failed.
You use this as follows:
class X:
def Iter(self):
yield 1
yield 2
yield 3
yield 4
xi = X().Iter()
print json.dumps(xi, cls = IterEncoder)
print json.dumps(xrange(4), cls = IterEncoder)
Which yields the expected:
[1, 2, 3, 4] [0, 1, 2, 3]
Don’t be overly tempted to check the type of o: it may be types.GeneratorType or types.XRangeType or perhaps even something else that I haven’t found out yet.