[Twisted-web] JSON "page" and nevow

Vincent Bernat bernat at luffy.cx
Sat Aug 16 08:36:47 EDT 2008


There are several  pages that I would like to serve  as JSON page. Nevow
include a  JSON serializer but it seems  there is no facility  to send a
page as JSON.

Here is what I do:

| class JsonPage(rend.Page):
|     docFactory = loaders.stan ( T.div(render=T.directive("json"),
|                                       data=T.directive("json")))
|     addSlash = True
|     flattenFactory = lambda self, *args: flat.flattenFactory(*args)
|     def render_json(self, ctx, data):
|         """Render the given data in a proper JSON string"""
|         def sanitize(data, d=None):
|             """Nevow JSON serializer is not able to handle some types.
|             We convert those types in proper types:
|              - string to unicode string
|              - PgSQL result set into list
|              - handling of deferreds
|             """
|             if type(data) in [list, tuple] or isinstance(data, PgSQL.PgResultSet):
|                 return [sanitize(x, d) for x in data]
|             if type(data) == str:
|                 return unicode(data)
|             if isinstance(data, rend.Fragment):
|                 io = StringIO()
|                 writer = io.write
|                 finisher = lambda result: io.getvalue()
|                 newctx = context.PageContext(parent=ctx, tag=data)
|                 data.rememberStuff(newctx)
|                 doc = data.docFactory.load()
|                 newctx = context.WovenContext(newctx, T.invisible[doc])
|                 fl = self.flattenFactory(doc, newctx, writer, finisher)
|                 fl.addCallback(sanitize, None)
|                 d.append(fl)
|                 return fl
|             if isinstance(data, defer.Deferred):
|                 if data.called:
|                     return sanitize(data.result)
|                 return data
|             if isinstance(data, failure.Failure):
|                 return unicode(
|                     "<span class='error'>An error occured (%s)</span>" % data.getErrorMessage())
|             return data
|         def serialize(data):
|             return json.serialize(sanitize(data))
|         d = []
|         data = sanitize(data, d)
|         d = defer.DeferredList(d)
|         d.addCallback(lambda x: serialize(data))
|         return d
|     def beforeRender(self, ctx):
|         inevow.IRequest(ctx).setHeader("Content-Type", "application/json; charset=UTF-8")

I inherit of this class and  just implement data_json. The main point is
that I should be able what I do for regular web pages, including the use
of deferred.

There are several drawbacks:
 - I need to use deferred.called and deferred.result that is not part of
   the public API. I don't know  how to avoid this. JSON serializer does
   not handle deferred (Fragments are rendered synchronously).
 - to  render Fragment,  I  use some  obscur  tricks that  I stole  from
   rend.py. Not sure that this would survive.
 - for Fragment,  serializers will turn  "<" into "&lt;".   Therefore, I
   should turn  them back as "<" on  Javascript side. I did  not found a
   way to avoid serializing twice.  It seems that T.xml instead of T.div
   would  do  the  trick  but  it  does  not  support  data  and  render
 - Nevow  JSON serializer  seems  not  te be  extended  to support  more
   types. Is it some plans to allow this?

Any tip to simplify or enhance the above code would be welcome.

Write and test a big program in small pieces.
            - The Elements of Programming Style (Kernighan & Plauger)

More information about the Twisted-web mailing list