Reputation: 447
I know I can do:
import datetime
def default(o):
if type(o) is datetime.datetime:
return o.isoformat()
data = {'a': datetime.datetime.today()}
json.dumps(data, default=default)
# '{"a": "2015-01-22T01:04:23.121392"}'
That works perfect. Now what if I have datetimes as my keys:
data = {datetime.datetime.today(): 'a'}
If I try the same it fails with a:
TypeError: keys must be a string
Is there any way I can do something similar, i.e. a custom converter but for keys?
Note: This is just a simple example. I have a deep nested dict structure where some keys are not strings.
EDIT: A nested example, but note that I don't have control over the data structure, it comes from an external function:
data = {'a': {datetime.datetime.today(): '1'}}
Upvotes: 10
Views: 7212
Reputation: 876
This is a generic example that will work with any data object that have a __str__
/ str(foo)
method associated, converting keys or values recursivally:
class MyJSONEncoder(json.JSONEncoder):
def default(self, o):
'''Default serialization for JSON of the specific objects used inside the dataclasses.'''
return str(o)
def dict_keys_to_str(self, o):
import datetime
if isinstance(o, dict):
for key in list(o.keys()):
o[key] = self.dict_keys_to_str(o[key])
if key is not None and not isinstance(key, (str, int, float, bool)):
o.update({str(key): o.pop(key)})
return o
def encode(self, obj):
return super().encode(self.dict_keys_to_str(obj))
# Call the conversion.
json_output = json.dumps(dictionary, cls=MyJSONEncoder)
Upvotes: 0
Reputation: 985
Upon Andrew Magee's and add nested list/set support:
import json
from datetime import datetime
class DatesToStrings(json.JSONEncoder):
def _encode(self, obj):
def transform_date(o):
return self._encode(o.isoformat() if isinstance(o, datetime) else o)
if isinstance(obj, dict):
return {transform_date(k): transform_date(v) for k, v in obj.items()}
elif isinstance(obj, list) or isinstance(obj, set):
return [transform_date(l) for l in obj]
else:
return obj
def encode(self, obj):
return super(DatesToStrings, self).encode(self._encode(obj))
print(json.dumps({"a": datetime.now()}, cls=DatesToStrings))
print(json.dumps({datetime.now(): 1}, cls=DatesToStrings))
print(json.dumps({"a": {datetime.now(): 3}}, cls=DatesToStrings))
print(json.dumps({"a": [datetime.now()]}, cls=DatesToStrings))
print(json.dumps({"a": {1: [datetime.now()]}}, cls=DatesToStrings))
print(json.dumps({"a": [{1: [datetime.now()]}]}, cls=DatesToStrings))
print(json.dumps({"a": {datetime.now()}}, cls=DatesToStrings))
Upvotes: 3
Reputation: 6684
You could do this:
class DatesToStrings(json.JSONEncoder):
def _encode(self, obj):
if isinstance(obj, dict):
def transform_date(o):
return self._encode(o.isoformat() if isinstance(o, datetime) else o)
return {transform_date(k): transform_date(v) for k, v in obj.items()}
else:
return obj
def encode(self, obj):
return super(DatesToStrings, self).encode(self._encode(obj))
>>> json.dumps({"a": {datetime.now(): 3}}, cls=DatesToStrings)
'{"a": {"2015-01-22T11:49:25.910261": 3}}'
Upvotes: 8
Reputation: 2967
Try this:
import datetime
import json
data = {1:{datetime.datetime.today(): 'a'}, 2:{datetime.datetime.today(): 'a'}}
dataString = repr(data)
dataString
#"{1: {datetime.datetime(2015, 1, 21, 16, 56, 15, 219567): 'a'}, 2: {datetime.datetime(2015, 1, 21, 16, 56, 15, 219567): 'a'}}"
dataDictionary = eval(dataString)
dataDictionary
#{1: {datetime.datetime(2015, 1, 21, 16, 56, 15, 219567): 'a'}, 2: {datetime.datetime(2015, 1, 21, 16, 56, 15, 219567): 'a'}}
datajsonString = json.dumps(dataString)
#'"{1: {datetime.datetime(2015, 1, 21, 16, 56, 15, 219567): \'a\'}, 2: {datetime.datetime(2015, 1, 21, 16, 56, 15, 219567): \'a\'}}"'
Upvotes: 0
Reputation: 3387
You can use a recursive function to fix the keys, including keys in nested dictionaries, before passing it to the serializer.
def datetime_key_fix(o):
if isinstance(o, dict):
for key in o:
o[key] = datetime_key_fix(o[key])
if type(key) is datetime.datetime:
o[key.isoformat()] = o[key]
del o[key]
return o
data = {datetime.datetime.today(): {datetime.datetime.today(): "a"}}
print json.dumps(datetime_key_fix(data))
Upvotes: 0
Reputation: 2620
If you are serializing for your python program to read at a later time, use pickle
module to do this. It will preserve custom classes and objects as long as the definitions are visible to the script/module that is going to deserialize it and use it.
You could do something like:
data = {datetime.datetime.today(): 'a'}
try:
import cPickle as pickle # Try it. It could be faster
except:
import pickle # Regular pickle as a fallback
with open("c:/mypickle.DAT", "w") as f:
pickle.dump(data, f)
If writing to physical disk is not what you want, especially for performance reasons, you can try to write to a file-like object, like StringIO.
Upvotes: 1
Reputation: 3191
Here is the recursive version - note that I won't guarantee it will be any faster than the pickled version:
def dictRecursiveFormat(d):
for key, val in list(d.items()):
if isinstance(key, datetime.datetime):
val = d.pop(key)
d[str(key)] = val
if isinstance(val, datetime.datetime) and isinstance(key, datetime.datetime):
d[str(key)] = str(val)
elif isinstance(val, datetime.datetime):
d[key] = str(val)
if type(val) is dict:
dictRecursiveFormat(val)
example:
In [52]: d= {'a': datetime.datetime.now(), 'b': {datetime.datetime.now(): datetime.datetime.now()}}
In [53]: dictRecursiveFormat(d)
In [54]: d
Out[54]:
{'a': '2015-01-21 19:33:52.293182',
'b': {'2015-01-21 19:33:52.293240': '2015-01-21 19:33:52.293229'}}
Upvotes: 5
Reputation: 3782
Just use str to change the type will be ok:
>>> import datetime
>>> type(datetime.datetime.today())
<type 'datetime.datetime'>
>>> data = {str(datetime.datetime.today()): 'a'}
>>> data
{'2015-01-22 08:13:11.554000': 'a'}
>>> data = {repr(datetime.datetime.today()): 'a'}
>>> data
{'datetime.datetime(2015, 1, 22, 8, 15, 0, 551000)': 'a'}
>>> data = {'a': {datetime.datetime.today(): '1'}}
>>> data
{'a': {datetime.datetime(2015, 1, 22, 8, 32, 25, 175000): '1'}}
Upvotes: 1