jsonpickle datetime to readable json format
Asked Answered
Z

3

8

Is it possible to convert datetime into a readable JSON format (which could be used from javascript)? Currently jsonpickle provides only a binary encoded value for datetime.

Zwinglian answered 16/10, 2013 at 21:57 Comment(0)
Z
1

After some trial and error I came up with the following solution:

class DatetimeHandler(jsonpickle.handlers.BaseHandler):
    def flatten(self, obj, data):
        return obj.strftime('%Y-%m-%d %H:%M:%S.%f')

jsonpickle.handlers.registry.register(datetime, DatetimeHandler)

encoded_datetime = jsonpickle.encode(datetime.now())
print(encoded_datetime)
decode_datetime = jsonpickle.decode(encoded_datetime)
print(decode_datetime)
Zwinglian answered 17/10, 2013 at 8:58 Comment(1)
the resulting decode_datetime is typed unicode, not the original datetime. The first answer is better suitable for marking as accepted.Sightly
P
6

There are several gotchas here:

First, please don't traffic in timezone unaware datetime objects. You will feel pain, not today, maybe not tomorrow, but someday. You can learn from others' mistakes (mine), or you can learn the hard way. As far as I'm concerned the fact that Python allows you to make datetime objects without a timezone at all is a bug.

Second, you can't reliably roundtrip strftime() and strptime() for timezone-aware datetime objects. This was fixed for UTC in Python 3.6, but still fails for other timezones.

Instead use datetime.isoformat() and datetime.fromisoformat(). These were added to the datetime class in Python 3.7 (and backported to earlier versions).

Third, jsonpickle does not clearly document how to roll your own DatetimeHandler. So yeah, you just want something legible that you send to Javascript or whatever? The solutions above will be fine. You want something that's legible but you also want to pull it back into Python at some point? Um, trickier.

Here's a hint: when you are subclassing a library to extend its capability, look carefully at the superclass you are extending.

I would have written DatetimeHandler somewhat differently. But the following works, and contains all my hard won wisdom on the subject. Ouch.

import pytz
import jsonpickle
from datetime import datetime

class Blah(object):

    def __init__(self, blah):
        self.datetime = datetime.now(pytz.utc)
        self.blah = blah

    def to_json(self):
        return jsonpickle.encode(self)

    @classmethod
    def from_json(cls, json_str):
        return jsonpickle.decode(json_str)

class DatePickleISO8601(jsonpickle.handlers.DatetimeHandler):
    def flatten(self, obj, data):
        pickler = self.context
        if not pickler.unpicklable:
            return str(obj)
        cls, args = obj.__reduce__()
        flatten = pickler.flatten
        payload = obj.isoformat()
        args = [payload] + [flatten(i, reset=False) for i in args[1:]]
        data['__reduce__'] = (flatten(cls, reset=False), args)
        return data

    def restore(self, data):
        cls, args = data['__reduce__']
        unpickler = self.context
        restore = unpickler.restore
        cls = restore(cls, reset=False)
        value = datetime.fromisoformat(args[0])
        return value

jsonpickle.handlers.registry.register(datetime, DatePickleISO8601)
Policewoman answered 25/2, 2016 at 17:59 Comment(3)
Absolutely! Please do.Policewoman
This answer is superior to the accepted one because it is reversible. The whole point of jsonpickle is that, as its name suggests, it is an analogue of pickle, so reversibility is implied and important.Prelate
Extended question. I am receiving data from an API that jsonpickle renders as "date": {"py/object": "datetime.date", "__reduce__": [{"py/type": "datetime.date"}, ["B+ULEA=="]]}. The only suggestion on this page that works for me is unpicklable=False, but it causes the data returned to be massive — 100Mb or more. None of the others have any effect. Any suggestions?Frenchman
D
5

With current version of jsonpickle (obtained from pip as of today), it seems plain use of encode just works, if setting unplickable to false:

>>> import jsonpickle
>>> from datetime import datetime
>>> jsonpickle.encode(datetime.now(), unpicklable=False)
'"2014-05-25 20:24:30.357299"'

However, your trick can be used to produce ISO 8601 format, which could be more adequate, accoding to ECMAScript v5 specs:

>>> class DatetimeHandler(jsonpickle.handlers.BaseHandler):
...     def flatten(self, obj, data):
...             return obj.isoformat()
...
>>> jsonpickle.handlers.registry.register(datetime, DatetimeHandler)
>>> jsonpickle.encode(datetime.now(), unpicklable=False)
'"2014-05-25T20:31:30.422826"'
Denotative answered 25/5, 2014 at 18:35 Comment(0)
Z
1

After some trial and error I came up with the following solution:

class DatetimeHandler(jsonpickle.handlers.BaseHandler):
    def flatten(self, obj, data):
        return obj.strftime('%Y-%m-%d %H:%M:%S.%f')

jsonpickle.handlers.registry.register(datetime, DatetimeHandler)

encoded_datetime = jsonpickle.encode(datetime.now())
print(encoded_datetime)
decode_datetime = jsonpickle.decode(encoded_datetime)
print(decode_datetime)
Zwinglian answered 17/10, 2013 at 8:58 Comment(1)
the resulting decode_datetime is typed unicode, not the original datetime. The first answer is better suitable for marking as accepted.Sightly

© 2022 - 2024 — McMap. All rights reserved.