3

I'm attempting to send a list of records in response to an Ajax query. This works well unless the results include a datetime field when my process fails with the error datetime.date(2011, 11, 1) is not JSON serializable.

I attempted to combine the answer I found to a very similar question here with instructions in the CherryPy documentation to use a custom json_out encoder, but it's not clear to me what signature that function must have. The function I wrote is:

 def json_encoder(thing):

      if hasattr(thing, 'isoformat'):
           return thing.isoformat()
      else:
           return str(thing)

and now any use of json_out (even with no datetime in the output) gives me the error TypeError: json_encoder() takes exactly 1 argument (0 given). But if the encoder doesn't take an argument, how does it receive the object to encode?

(Also, I assume my use of str(thing) as the default method of encoding is wrong and that this should be done with a call to whatever the default handler for json encoding is, but I'm not sure how to call that method).

Community
  • 1
  • 1
Larry Lustig
  • 49,320
  • 14
  • 110
  • 160

3 Answers3

12

I got the same problem (Python 3.2, Cherrypy 3.2.2) and I solved it with the following code:

import cherrypy
import json
import datetime
class _JSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.date):
            return obj.isoformat()
        return super().default(obj)
    def iterencode(self, value):
        # Adapted from cherrypy/_cpcompat.py
        for chunk in super().iterencode(value):
            yield chunk.encode("utf-8")

json_encoder = _JSONEncoder()

def json_handler(*args, **kwargs):
    # Adapted from cherrypy/lib/jsontools.py
    value = cherrypy.serving.request._json_inner_handler(*args, **kwargs)
    return json_encoder.iterencode(value)

And then you can use the Cherrypy json_out decorator:

class Root:
     @cherrypy.expose
     @cherrypy.tools.json_out(handler=json_handler)
     def default(self, *args, **kwargs):
         ...
Pierre Le Marre
  • 121
  • 1
  • 3
10

I do the next in a similar case:

class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            return float(obj)
        return json.JSONEncoder.default(self, obj)

and at the call:

json.dumps(my_variable, cls=DecimalEncoder)

So in your case it should be like:

class DateEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, 'isoformat'):
            return obj.isoformat()
        else:
            return str(obj)
        return json.JSONEncoder.default(self, obj)


json.dumps(my_variable, cls=DateEncoder)
Daniel Lenkes
  • 306
  • 2
  • 8
  • Thanks, I have rewritten my encoder as a class by copying yours. However, I'm not calling json.dumps() but rather using CherryPy's json tools. Those tools can be configured with a custom handler for encoding, but they want a function, not a class or an instance. – Larry Lustig Nov 04 '11 at 16:07
  • I have now removed the CherryPy tool for JSON encoding and am calling simplejson.dumps() directly. With this configuration, the encoder as you showed me works correctly. Thank you! – Larry Lustig Nov 04 '11 at 16:10
  • Is this v3 specific ?? I could not get it to work on v2.7.5 . The steps i followed were the same. This does not work if i want to jsonify the list of my models (say A), where an attribute of A is datetime type. I believe where we want to retrieve only one model's JSON this wll work fine – saurshaz Sep 05 '13 at 09:24
  • How does the `DateEncoder::default()` function ever call the last `return` statement? – swdev Apr 29 '17 at 06:31
2

For custom json_handler implementation, see Pierre's excellent answer. However, specifying @cherrypy.tools.json_out(handler=json_handler) every time you use the tool is a bit cumbersome, so as the jsontools.json_out source code points out, it's better to use this:

cherrypy.config['tools.json_out.handler'] = json_handler

Also, you can enable tools on class level using _cp_config:

class Example
_cp_config = {
  'tools.json_out.on': True
}
user1338062
  • 11,939
  • 3
  • 73
  • 67