It is easy to serialize a Python data structure as JSON, we just need to call the json.dumps method, but if our data stucture contains a datetime object we'll get an exception:

TypeError: datetime.datetime(...) is not JSON serializable

How can we fix this?

In the first example we can see the problem:

examples/python/datetime_json_fails.py

import json
import datetime

d = {
   'name' : 'Foo'
}
print(json.dumps(d))   # {"name": "Foo"}

d['date'] = datetime.datetime.now()
print(json.dumps(d))   # TypeError: datetime.datetime(2016, 4, 8, 11, 22, 3, 84913) is not JSON serializable

The first call to json.dumps works properly, but once we add a key with a value that is a datetime object, the call throws an exception.

The solution

The solution is quite simple. The json.dumps method can accept an optional parameter called default which is expected to be a function. Every time JSON tries to convert a value it does not know how to convert it will call the function we passed to it. The function will receive the object in question, and it is expected to return the JSON representation of the object.

In the function we just call the __str__ method of the datetime object that will return a string representation of the value. This is what we return.

While the condition we have in the function is not required, if we expect to have other types of objects in our data structure that need special treatment, we can make sure our function handles them too. As dealing with each object will probably be different we check if the current object is one we know to handle and do that inside the if statement.

examples/python/datetime_json.py

import json
import datetime

d = {
   'name' : 'Foo'
}
print(json.dumps(d))   # {"name": "Foo"}

d['date'] = datetime.datetime.now()

def myconverter(o):
    if isinstance(o, datetime.datetime):
        return o.__str__()

print(json.dumps(d, default = myconverter))    # {"date": "2016-04-08 11:43:36.309721", "name": "Foo"}


Other representation of datetime

The string representation that __str__ might match our needs, but if not we have other options. We can use the __repr__ method to return the following:

{"date": "datetime.datetime(2016, 4, 8, 11, 43, 54, 920632)", "name": "Foo"}

We can even hand-craft something like this:

        return "{}-{}-{}".format(o.year, o.month, o.day)

That will return the following:

{"date": "2016-4-8", "name": "Foo"}