Explain Codes LogoExplain Codes Logo

How to overcome "datetime.datetime not JSON serializable"?

python
json-serialization
datetime
iso-8601
Anton ShumikhinbyAnton Shumikhin·Sep 10, 2024
TLDR

The quick solution to serialize datetime objects in JSON is to use .isoformat() and json.dumps():

import json from datetime import datetime # Serialize datetime object to JSON string json_data = json.dumps(datetime.now().isoformat())

The datetime object is now in a JSON-friendly string format, voila!

Quick transformations with default=str

To convert datetime objects quickly when precision isn't critical, use default=str in json.dumps():

import json from datetime import datetime data_with_datetime = {'timestamp': datetime.now()} # It's morphin' time! datetime to string json_data = json.dumps(data_with_datetime, default=str)

Heads up! This approach will convert all non-serializable objects to strings which can cause issues during deserialization.

Custom JSON serialization: json_serial

You can specifically handle datetime objects, preserving their type during deserialization using a custom serialization function:

import json from datetime import datetime def json_serial(obj): """JSON serializer for those pesky types Python refuses to comprehend natively.""" if isinstance(obj, (datetime, datetime.date)): return obj.isoformat() raise TypeError ("This isn't the type you're looking for") json_data = json.dumps(data_with_datetime, default=json_serial)

The json_serial function ensures datetime objects become ISO 8601 strings, while raising a fuss (TypeError) for unhandled types.

Timestamp precision: subclassing json.JSONEncoder

For a more granular control and extendability, create a subclass, DateTimeEncoder, of json.JSONEncoder:

import json from datetime import datetime class DateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, (datetime, datetime.date)): return obj.isoformat() # Let superman handle the rest return super(DateTimeEncoder, self).default(obj) json_data = json.dumps(data_with_datetime, cls=DateTimeEncoder)

Not only does this encoder handles datetime serilization with precision, but it can be extended to handle more complex types later.

Special mention: json_util

Working with MongoDB? Use pymongo's json_util for an optimal datetime handling:

from bson import json_util import json # For those precious MongoDB timestamps json_data = json.dumps(data_with_datetime, default=json_util.default)

To deserialize, json.loads with json_util.object_hook can revert the datetime objects.

Visualization

Pretend that a datetime object is like a water balloon trying to fit into a stack of bricks.

Plain JSON might scream, "No way!", but serializing datetime helps defuse the tension:

Before Serialization: 🎈 (datetime.datetime) ➡️ 🧱🧱🧱 (JSON)

This is incompatible because the water balloon won't stack with the bricks.

However, when we serialize the datetime:

After Serialization: 🎈🔄🧱 (datetime.isoformat()) ➡️ 🧱🧱🧱 (JSON)

Voila! The water balloon transformed into a brick (ISO 8601) fits perfectly into the JSON brick stack.

Precision versus Practicality

.isoformat() is great but what about precision and deserialization? Let's discuss:

  • Full datetime precision: If datetime serialization should include microseconds or timezone information, ensure .isoformat() caters to these.
  • Deserialization: When you deserialize, if you need the exact datetime type back, you'll need a paired parsing step.
  • Selecting encoders: Choose wisely! Django's built-in DjangoJSONEncoder supports datetime but may miss milliseconds.

Understand these aspects to select correctly for your specific needs.

ISO 8601: Making JavaScript Happy

An advantage of serializing datetime into ISO 8601 format is its compatibility with JavaScript's Date parsing:

// In a web application: const isoDateString = '2021-12-31T23:59:59.000Z'; const dateObject = new Date(isoDateString);

This provides seamless integration with front-end applications and follows best practices in web development.