Python JSON encoder to support datetime?
The docs suggest subclassing JSONEncoder and implementing your own default method. Seems like you're basically there, and it's not a "dirty hack".
The reason dates aren't handled by the default encoder is there is no standard representation of a date in JSON. Some people are using the format /Date(1198908717056)/
, but I prefer ISO format personally.
import jsonimport datetimeclass DateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, (datetime.datetime, datetime.date, datetime.time)): return obj.isoformat() elif isinstance(obj, datetime.timedelta): return (datetime.datetime.min + obj).time().isoformat() return super(DateTimeEncoder, self).default(obj)now = datetime.datetime.now()encoder = DateTimeEncoder()encoder.encode({"datetime": now, "date": now.date(), "time": now.time()})> {"datetime": "2019-07-02T16:17:09.990126", "date": "2019-07-02", "time": "16:17:09.990126"}
I made my own classes for my project:
import datetimeimport decimalimport jsonimport sysclass EnhancedJSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): ARGS = ('year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond') return {'__type__': 'datetime.datetime', 'args': [getattr(obj, a) for a in ARGS]} elif isinstance(obj, datetime.date): ARGS = ('year', 'month', 'day') return {'__type__': 'datetime.date', 'args': [getattr(obj, a) for a in ARGS]} elif isinstance(obj, datetime.time): ARGS = ('hour', 'minute', 'second', 'microsecond') return {'__type__': 'datetime.time', 'args': [getattr(obj, a) for a in ARGS]} elif isinstance(obj, datetime.timedelta): ARGS = ('days', 'seconds', 'microseconds') return {'__type__': 'datetime.timedelta', 'args': [getattr(obj, a) for a in ARGS]} elif isinstance(obj, decimal.Decimal): return {'__type__': 'decimal.Decimal', 'args': [str(obj),]} else: return super().default(obj)class EnhancedJSONDecoder(json.JSONDecoder): def __init__(self, *args, **kwargs): super().__init__(*args, object_hook=self.object_hook, **kwargs) def object_hook(self, d): if '__type__' not in d: return d o = sys.modules[__name__] for e in d['__type__'].split('.'): o = getattr(o, e) args, kwargs = d.get('args', ()), d.get('kwargs', {}) return o(*args, **kwargs)if __name__ == '__main__': j1 = json.dumps({'now': datetime.datetime.now(), 'val': decimal.Decimal('9.3456789098765434987654567')}, cls=EnhancedJSONEncoder) print(j1) o1 = json.loads(j1, cls=EnhancedJSONDecoder) print(o1)
Result:
{"val": {"args": ["9.3456789098765434987654567"], "__type__": "decimal.Decimal"}, "now": {"args": [2014, 4, 29, 11, 44, 57, 971600], "__type__": "datetime.datetime"}}{'val': Decimal('9.3456789098765434987654567'), 'now': datetime.datetime(2014, 4, 29, 11, 44, 57, 971600)}
References:
- json Documentation
- Mark Hildreth -- Subclassing JSONEncoder and JSONDecoder
- Cédric Krier -- trytond.protocols.jsonrpc source code
Note: It can be made more flexible by passing a custom dictionary with types as keys and args, kwargs as values to the encoder's __init__()
and use that (or a default dictionary) in the default()
method.