#22821: DjangoJSONEncoder no longer supports simplejson
-------------------------------------+-------------------------------------
     Reporter:  Keryn Knight         |                    Owner:  nobody
  <django@…>                         |                   Status:  new
         Type:  New feature          |                  Version:  master
    Component:  Core                 |               Resolution:
  (Serialization)                    |             Triage Stage:
     Severity:  Normal               |  Unreviewed
     Keywords:                       |      Needs documentation:  0
    Has patch:  0                    |  Patch needs improvement:  0
  Needs tests:  0                    |                    UI/UX:  0
Easy pickings:  0                    |
-------------------------------------+-------------------------------------

Comment (by Keryn Knight <django@…>):

 Perhaps I'm not understanding the nuances of the problem (it wouldn't be
 the first time), but I'm not convinced that solving this particular
 problem is difficult (periphery around it, perhaps)
 The following code-bomb works for me using `simplejson` versions (the last
 ''PATCH'' releases of every ''MINOR'' version):
 * 2.1.6
 * 2.6.2
 * 3.0.9
 * 3.3.3
 * 3.4.1
 * 3.5.2
 and the `json` module in Python 2.7.6 (I'm guessing/hoping the least Py3K
 compatible part of this is using `xrange`, and `print` as a keyword)
 {{{
 import simplejson
 import json
 import decimal
 import datetime


 # So this is taken straight out of the DjangoJSONEncoder implementation
 # in master as of 7548aa8ffd46eb6e0f73730d1b2eb515ba581f95
 class RichEncoder(object):
     def default(self, o):
         # See "Date Time String Format" in the ECMA-262 specification.
         if isinstance(o, datetime.datetime):
             r = o.isoformat()
             if o.microsecond:
                 r = r[:23] + r[26:]
             if r.endswith('+00:00'):
                 r = r[:-6] + 'Z'
             return r
         elif isinstance(o, datetime.date):
             return o.isoformat()
         elif isinstance(o, datetime.time):
             if is_aware(o):
                 raise ValueError("JSON can't represent timezone-aware
 times.")
             r = o.isoformat()
             if o.microsecond:
                 r = r[:12]
             return r
         elif isinstance(o, decimal.Decimal):
             return str(o)
         else:
             return super(RichEncoder, self).default(o)

 # this line is what would end up as DjangoJSONEncoder
 # and also aliased as DateTimeAwareJSONEncoder
 class stdlibJSONEncoder(RichEncoder, json.JSONEncoder): pass

 # this would by necessity be a userland implementation, but is obviously
 less
 # code to copy-paste & maintain.
 class simpleJSONEncoder(RichEncoder, simplejson.JSONEncoder): pass


 data = {'decimal': decimal.Decimal('4.011'), 'datetime':
 datetime.datetime.today()}

 print "Naive:"
 # naive, stdlib
 try:
     json.dumps(data)
 except TypeError as e:
     print e
 # naive, simplejson
 try:
     simplejson.dumps(data)
 except TypeError as e:
     print e

 print "\n\nEncoding from Django:"
 print json.dumps(data, cls=stdlibJSONEncoder)
 print simplejson.dumps(data, cls=simpleJSONEncoder)
 # allow decimals to be strings
 print simplejson.dumps(data, cls=simpleJSONEncoder, use_decimal=False)


 class CustomJSONEncoder(object):
     def default(self, o):
         # contrived example ...
         if hasattr(o, '__iter__'):
             return tuple(o)
         return super(CustomJSONEncoder, self).default(o)

 # so you need to encode *other* things, but you want the date/decimal
 handling
 # both of these would obviously be userland implementations
 class stdlibCustomJSONEncoder(CustomJSONEncoder, stdlibJSONEncoder): pass
 class simpleCustomJSONEncoder(CustomJSONEncoder, simpleJSONEncoder): pass
 # long-winded version.
 class simpleCustomJSONEncoder2(CustomJSONEncoder, RichEncoder,
 simplejson.JSONEncoder): pass

 more_data = {'xrange': xrange(0, 10), 'datetime':
 datetime.datetime.today()}

 print "\n\nEncoding from Django, + extra stuff:"
 print json.dumps(more_data, cls=stdlibCustomJSONEncoder)
 print simplejson.dumps(more_data, cls=simpleCustomJSONEncoder)
 # long-winded version, as above
 print simplejson.dumps(more_data, cls=simpleCustomJSONEncoder2)

 print "\n\nWithout our custom encoder:"
 last_data = {'xrange': xrange(0, 10)}
 try:
     json.dumps(last_data)
 except TypeError as e:
     print e
 try:
     simplejson.dumps(last_data)
 except TypeError as e:
     print e
 }}}
 None of these exhibit the `namedtuple_as_object` issue presented in the
 ticket itself, because their encoders match their expectations.

 If `simplejson` is still buggy, 30-40 releases later, as the mentioned
 ticket indicated, that's fine; that's for an end-user to deal with, or for
 `simplejson` to handle in future releases, but as long as the contract for
 a JSONEncoder instance's default is just accept `o` and return a
 serializable version of it, I'd think the above would continue to work.

 By separating the custom `default` object introspection out of the encoder
 itself, it at least grants the possibility of users getting the best of
 both Django's code and simplejson's, if that is their desire. The danger
 of passing a `json.JSONEncoder` to `simplejson.dumps()` as that ticket
 describes, is then circumventable, though left as an exercise to the user.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/22821#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/095.7899460eeeabc67a3b7e0beef516e726%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to