views:

7088

answers:

6

I want to send a datetime.datetime object in serialized form from Python using JSON and de-serialize in JavaScript using JSON. What is the best way to do this?

A: 

I can address you to eGenix Python extension, containing a lot of functions for handling date and time. Plus, i've found this article with some code to deal with Python to Javascript marshaling.

Stefano Driussi
+8  A: 

If you're certain that only Javascript will be consuming the JSON, I prefer to pass Javascript Date objects directly.

The ctime() method on datetime objects will return a string that the Javascript Date object can understand.

import datetime
date = datetime.datetime.today()
json = '{"mydate":new Date("%s")}' % date.ctime()

Javascript will happily use that as an object literal, and you've got your Date object built right in.

Triptych
Technically not valid JSON, but it is a valid JavaScript object literal. (For the sake of principle I would set the Content-Type to text/javascript instead of application/json.) If the consumer will *always* and *forever* be *only* a JavaScript implementation, then yeah, this is pretty elegant. I would use it.
system PAUSE
+21  A: 

For cross language projects I found out that strings containing RfC 3339 dates are the best way to go. A RfC 3339 date looks like this:

  1985-04-12T23:20:50.52Z

I think most of the format is obvious. The only somewhat unusual thing may be the "Z" at the end. It stands for GMT/UTC. You could also add a timezone offset like +02:00 for CEST (Germany in summer). I personally prefer to keep everything in UTC until it is displayed.

For displaying, comparisons and storage you can leave it in string format across all languages. If you need the date for calculations easy to convert it back to a native date object in most language.

So generate the JSON like this:

  json.dump(datetime.now().strftime('%Y-%m-%dT%H:%M:%S'))

Unfortunately Javascripts Date constructor doesn't accept RfC 3339 strings but there are many parsers available on the Internet.

mdorseif
This date formatting mechanism is natively supported, both by `datetime`: datetime.isoformat()and by `simplejson`, which will dump `datetime` objects as `isoformat` strings by default. No need for manual `strftime` hacking.
jrk
@jrk - I'm not getting automatic conversion from `datetime` objects to the `isoformat` string. For me, `simplejson.dumps(datetime.now())` yields `TypeError: datetime.datetime(...) is not JSON serializable`
kostmo
`json.dumps(datetime.datetime.now().isoformat())` is where the magic happens.
jathanism
The beauty of simplejson is that if I have a complex data structure, it will parse it and turn it into JSON. If I have to do json.dumps(datetime.datetime.now().isoformat()) for every datetime object, I lose that. Is there a way to fix this?
superjoe30
superjoe30: see http://stackoverflow.com/questions/455580/json-datetime-between-python-and-javascript/2680060#2680060 on how to do that
mdorseif
yes, http://stackoverflow.com/questions/455580/json-datetime-between-python-and-javascript/2680060#2680060 is the better solution
Nick Franceschina
+18  A: 

You can add the 'default' parameter to json.dumps to handle this:

>>> dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None
>>> json.dumps(datetime.datetime.now(), default=dthandler)
'"2010-04-20T20:08:21.634121"'

A more comprehensive default handler function:

def handler(obj):
    if isinstance(obj, datetime.datetime):
        return obj.isoformat()
    elif isinstance(obj, ...):
        return ...
    else:
        raise TypeError, 'Object of type %s with value of %s is not JSON serializable' % (type(Obj), repr(Obj))

Update: Added output of type as well as value.

JT
this solution is much simpler than mdorseif's. Working great in my django app.
superjoe30
The problem is that if you have some other objects in list/dict this code will convert them to None.
Tomasz Wysocki
json.dumps won't know how to convert those either, but the exception is being supressed. Sadly a one line lambda fix has it's shortcomings. If you would rather have an exception raised on the unknowns (which is a good idea) use the function I've added above.
JT
the full output format should have timezone on it as well... and isoformat() does not provide this functionality... so you should make sure to append that info on the string before returning
Nick Franceschina
+7  A: 

If you are using simplejson, you can subclass JSONEncoder and override the default() method to provide your own custom serializers:

class JSONEncoder(simplejson.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.strftime('%Y-%m-%dT%H:%M:%S')
        else:
            return simplejson.JSONEncoder.default(self, obj)

Then, you can call it like this:

>>> JSONEncoder().encode([datetime.datetime.now()])
'["2010-06-15T14:42:28"]'
ramen
+3  A: 

Here's a fairly complete solution for recursively encoding and decoding datetime.datetime and datetime.date objects using the standard library json module. This needs Python >= 2.6 since the %f format code in the datetime.datetime.strptime() format string is only supported in since then. For Python 2.5 support, drop the %f and strip the microseconds from the ISO date string before trying to convert it, but you'll loose microseconds precision, of course. For interoperability with ISO date strings from other sources, which may include a time zone name or UTC offset, you may also need to strip some parts of the date string before the conversion. For a complete parser for ISO date strings (and many other date formats) see the third-party dateutil module.

Decoding only works when the ISO date strings are values in a JavaScript literal object notation or in nested structures within an object. ISO date strings, which are items of a top-level array will not be decoded.

I.e. this works:

date = datetime.datetime.now()
>>> json = dumps(dict(foo='bar', innerdict=dict(date=date)))
>>> json
'{"innerdict": {"date": "2010-07-15T13:16:38.365579"}, "foo": "bar"}'
>>> loads(json)
{u'innerdict': {u'date': datetime.datetime(2010, 7, 15, 13, 16, 38, 365579)},
u'foo': u'bar'}

And this too:

>>> json = dumps(['foo', 'bar', dict(date=date)])
>>> json
'["foo", "bar", {"date": "2010-07-15T13:16:38.365579"}]'
>>> loads(json)
[u'foo', u'bar', {u'date': datetime.datetime(2010, 7, 15, 13, 16, 38, 365579)}]

But this doesn't work as expected:

>>> json = dumps(['foo', 'bar', date])
>>> json
'["foo", "bar", "2010-07-15T13:16:38.365579"]'
>>> loads(json)
[u'foo', u'bar', u'2010-07-15T13:16:38.365579']

Here's the code:

__all__ = ['dumps', 'loads']

import datetime

try:
    import json
except ImportError:
    import simplejson as json

class JSONDateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (datetime.date, datetime.datetime)):
            return obj.isoformat()
        else:
            return json.JSONEncoder.default(self, obj)

def datetime_decoder(d):
    if isinstance(d, list):
        pairs = enumerate(d)
    elif isinstance(d, dict):
        pairs = d.items()
    result = []
    for k,v in pairs:
        if isinstance(v, basestring):
            try:
                # The %f format code is only supported in Python >= 2.6.
                # For Python <= 2.5 strip off microseconds
                # v = datetime.datetime.strptime(v.rsplit('.', 1)[0],
                #     '%Y-%m-%dT%H:%M:%S')
                v = datetime.datetime.strptime(v, '%Y-%m-%dT%H:%M:%S.%f')
            except ValueError:
                try:
                    v = datetime.datetime.strptime(v, '%Y-%m-%d').date()
                except ValueError:
                    pass
        elif isinstance(v, (dict, list)):
            v = datetime_decoder(v)
        result.append((k, v))
    if isinstance(d, list):
        return [x[1] for x in result]
    elif isinstance(d, dict):
        return dict(result)

def dumps(obj):
    return json.dumps(obj, cls=JSONDateTimeEncoder)

def loads(obj):
    return json.loads(obj, object_hook=datetime_decoder)

if __name__ == '__main__':
    mytimestamp = datetime.datetime.utcnow()
    mydate = datetime.date.today()
    data = dict(
        foo = 42,
        bar = [mytimestamp, mydate],
        date = mydate,
        timestamp = mytimestamp,
        struct = dict(
            date2 = mydate,
            timestamp2 = mytimestamp
        )
    )

    print repr(data)
    jsonstring = dumps(data)
    print jsonstring
    print repr(loads(jsonstring))